From 6d7735e7f0112ba0bdffd57f8197fccb0a0da84a Mon Sep 17 00:00:00 2001 From: Byron Nikolaidis Date: Tue, 29 Dec 1998 01:49:58 +0000 Subject: [PATCH] Update for version 6-40-0002 and re-merge Thomas' changes. --- src/interfaces/odbc/bind.c | 9 +- src/interfaces/odbc/bind.h | 1 + src/interfaces/odbc/connection.c | 144 ++------ src/interfaces/odbc/connection.h | 48 +-- src/interfaces/odbc/convert.c | 356 +++++++++++++++----- src/interfaces/odbc/convert.h | 6 +- src/interfaces/odbc/dlg_specific.c | 17 + src/interfaces/odbc/dlg_specific.h | 4 + src/interfaces/odbc/environ.c | 23 +- src/interfaces/odbc/execute.c | 39 ++- src/interfaces/odbc/info.c | 103 ++++-- src/interfaces/odbc/misc.c | 4 +- src/interfaces/odbc/misc.h | 44 ++- src/interfaces/odbc/options.c | 508 +++++++++++++++++++++-------- src/interfaces/odbc/pgtypes.c | 103 +++++- src/interfaces/odbc/pgtypes.h | 2 + src/interfaces/odbc/psqlodbc.c | 1 - src/interfaces/odbc/psqlodbc.h | 25 +- src/interfaces/odbc/psqlodbc.rc | 10 +- src/interfaces/odbc/qresult.c | 128 ++++++-- src/interfaces/odbc/qresult.h | 12 +- src/interfaces/odbc/resource.h | 1 + src/interfaces/odbc/results.c | 430 ++++++++++++++++++------ src/interfaces/odbc/socket.h | 10 + src/interfaces/odbc/statement.c | 85 ++++- src/interfaces/odbc/statement.h | 27 +- 26 files changed, 1568 insertions(+), 572 deletions(-) diff --git a/src/interfaces/odbc/bind.c b/src/interfaces/odbc/bind.c index 30e76af255..512f352181 100644 --- a/src/interfaces/odbc/bind.c +++ b/src/interfaces/odbc/bind.c @@ -128,12 +128,13 @@ static char *func="SQLBindParameter"; stmt->parameters[ipar].EXEC_buffer = NULL; } - if (pcbValue && *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET) + /* Data at exec macro only valid for C char/binary data */ + if ((fSqlType == SQL_LONGVARBINARY || fSqlType == SQL_LONGVARCHAR) && pcbValue && *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET) stmt->parameters[ipar].data_at_exec = TRUE; else stmt->parameters[ipar].data_at_exec = FALSE; - mylog("SQLBindParamater: ipar = %d, *pcbValue = %d, data_at_exec = %d\n", ipar, pcbValue ? *pcbValue: -777, stmt->parameters[ipar].data_at_exec); + mylog("SQLBindParamater: ipar=%d, paramType=%d, fCType=%d, fSqlType=%d, cbColDef=%d, ibScale=%d, rgbValue=%d, *pcbValue = %d, data_at_exec = %d\n", ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, pcbValue ? *pcbValue: -777, stmt->parameters[ipar].data_at_exec); return SQL_SUCCESS; } @@ -195,6 +196,9 @@ mylog("**** SQLBindCol: stmt = %u, icol = %d\n", stmt, icol); icol--; /* use zero based col numbers from here out */ + /* Reset for SQLGetData */ + stmt->bindings[icol].data_left = -1; + if (rgbValue == NULL) { /* we have to unbind the column */ stmt->bindings[icol].buflen = 0; @@ -357,6 +361,7 @@ int i; new_bindings[i].buflen = 0; new_bindings[i].buffer = NULL; new_bindings[i].used = NULL; + new_bindings[i].data_left = -1; } return new_bindings; diff --git a/src/interfaces/odbc/bind.h b/src/interfaces/odbc/bind.h index 7d19064e76..39e594f346 100644 --- a/src/interfaces/odbc/bind.h +++ b/src/interfaces/odbc/bind.h @@ -17,6 +17,7 @@ */ struct BindInfoClass_ { Int4 buflen; /* size of buffer */ + Int4 data_left; /* amount of data left to read (SQLGetData) */ char *buffer; /* pointer to the buffer */ Int4 *used; /* used space in the buffer (for strings not counting the '\0') */ Int2 returntype; /* kind of conversion to be applied when returning (SQL_C_DEFAULT, SQL_C_CHAR...) */ diff --git a/src/interfaces/odbc/connection.c b/src/interfaces/odbc/connection.c index c07b051070..bd82f5e7ba 100644 --- a/src/interfaces/odbc/connection.c +++ b/src/interfaces/odbc/connection.c @@ -31,7 +31,6 @@ extern GLOBAL_VALUES globals; -// void CC_test(ConnectionClass *self); RETCODE SQL_API SQLAllocConnect( HENV henv, @@ -252,6 +251,14 @@ ConnectionClass *rv; rv->translation_handle = NULL; rv->DataSourceToDriver = NULL; rv->DriverToDataSource = NULL; + + + /* Initialize statement options to defaults */ + /* Statements under this conn will inherit these options */ + + InitializeStatementOptions(&rv->stmtOptions); + + } return rv; } @@ -337,7 +344,7 @@ QResultClass *res; mylog("CC_abort: sending ABORT!\n"); - res = CC_send_query(self, "ABORT", NULL, NULL); + res = CC_send_query(self, "ABORT", NULL); CC_set_no_trans(self); if (res != NULL) @@ -664,7 +671,7 @@ static char *func="CC_connect"; /* database really exists on the server machine */ mylog("sending an empty query...\n"); - res = CC_send_query(self, " ", NULL, NULL); + res = CC_send_query(self, " ", NULL); if ( res == NULL || QR_get_status(res) != PGRES_EMPTY_QUERY) { mylog("got no result from the empty query. (probably database does not exist)\n"); self->errornumber = CONNECTION_NO_SUCH_DATABASE; @@ -691,8 +698,6 @@ static char *func="CC_connect"; CC_send_settings(self); CC_lookup_lo(self); /* a hack to get the oid of our large object oid type */ - // CC_test(self); - CC_clear_error(self); /* clear any initial command errors */ self->status = CONN_CONNECTED; @@ -812,9 +817,9 @@ int rv; 'declare cursor C3326857 for ...' and 'fetch 100 in C3326857' statements. */ QResultClass * -CC_send_query(ConnectionClass *self, char *query, QResultClass *result_in, char *cursor) +CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi) { -QResultClass *res = NULL; +QResultClass *result_in, *res = NULL; char id, swallow; SocketClass *sock = self->sock; static char msgbuffer[MAX_MESSAGE_LEN+1]; @@ -970,7 +975,7 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont swallow = SOCK_get_char(sock); if ((swallow != '\0') || SOCK_get_errcode(sock) != 0) { self->errornumber = CONNECTION_BACKEND_CRAZY; - self->errormsg = "Unexpected protocol character from backend"; + self->errormsg = "Unexpected protocol character from backend (send_query - I)"; res = QR_Constructor(); QR_set_status(res, PGRES_FATAL_ERROR); return res; @@ -1006,7 +1011,9 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont SOCK_get_string(sock, msgbuffer, MAX_MESSAGE_LEN); break; case 'T': /* Tuple results start here */ - if (result_in == NULL) { + result_in = qi ? qi->result_in : NULL; + + if ( result_in == NULL) { result_in = QR_Constructor(); mylog("send_query: 'T' no result_in: res = %u\n", result_in); if ( ! result_in) { @@ -1015,7 +1022,10 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont return NULL; } - if ( ! QR_fetch_tuples(result_in, self, cursor)) { + if (qi) + QR_set_cache_size(result_in, qi->row_size); + + if ( ! QR_fetch_tuples(result_in, self, qi ? qi->cursor : NULL)) { self->errornumber = CONNECTION_COULD_NOT_RECEIVE; self->errormsg = QR_get_message(result_in); return NULL; @@ -1040,7 +1050,7 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont return res; default: self->errornumber = CONNECTION_BACKEND_CRAZY; - self->errormsg = "Unexpected protocol character from backend"; + self->errormsg = "Unexpected protocol character from backend (send_query)"; CC_set_no_trans(self); mylog("send_query: error - %s\n", self->errormsg); @@ -1058,7 +1068,6 @@ static char msgbuffer[MAX_MESSAGE_LEN+1]; int i; mylog("send_function(): conn=%u, fnid=%d, result_is_int=%d, nargs=%d\n", self, fnid, result_is_int, nargs); -// qlog("conn=%u, func=%d\n", self, fnid); if (SOCK_get_errcode(sock) != 0) { self->errornumber = CONNECTION_COULD_NOT_SEND; @@ -1124,9 +1133,12 @@ int i; return FALSE; + case 'Z': + break; + default: self->errornumber = CONNECTION_BACKEND_CRAZY; - self->errormsg = "Unexpected protocol character from backend"; + self->errormsg = "Unexpected protocol character from backend (send_function, args)"; CC_set_no_trans(self); mylog("send_function: error - %s\n", self->errormsg); @@ -1178,7 +1190,7 @@ int i; default: self->errornumber = CONNECTION_BACKEND_CRAZY; - self->errormsg = "Unexpected protocol character from backend"; + self->errormsg = "Unexpected protocol character from backend (send_function, result)"; CC_set_no_trans(self); mylog("send_function: error - %s\n", self->errormsg); @@ -1352,107 +1364,3 @@ CC_log_error(char *func, char *desc, ConnectionClass *self) qlog("INVALID CONNECTION HANDLE ERROR: func=%s, desc='%s'\n", func, desc); } -/* -void -CC_test(ConnectionClass *self) -{ -static char *func = "CC_test"; -HSTMT hstmt1; -RETCODE result; -char pktab[255], fktab[255], pkcol[255], fkcol[255], tgname[255]; -SDWORD pktab_len, pkcol_len, fktab_len, fkcol_len, ur_len, dr_len, tgname_len; -SWORD cols, seq; -SDWORD update_rule, delete_rule; - - mylog( "%s: entering...\n", func); - - result = SQLAllocStmt( self, &hstmt1); - if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { - return; - } - - result = SQLPrimaryKeys(hstmt1, NULL, 0, NULL, 0, "t1", SQL_NTS); - - qlog("SQLPrimaryKeys result = %d\n", result); - - result = SQLNumResultCols(hstmt1, &cols); - qlog("cols SQLTables result = %d\n", result); - - result = SQLBindCol(hstmt1, 3, SQL_C_CHAR, pktab, sizeof(pktab), &pktab_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 4, SQL_C_CHAR, pkcol, sizeof(pkcol), &pkcol_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 5, SQL_C_SHORT, &seq, 0, NULL); - qlog("bind result = %d\n", result); - - result = SQLFetch(hstmt1); - qlog("SQLFetch result = %d\n", result); - while (result != SQL_NO_DATA_FOUND) { - qlog("fetch on stmt1: result = %d, pktab='%s', pkcol='%s', seq=%d\n", - result, pktab, pkcol, seq); - - result = SQLFetch(hstmt1); - } - qlog("SQLFetch result = %d\n", result); - - // Test of case #1 - result = SQLForeignKeys(hstmt1, "", SQL_NTS, "", SQL_NTS, "t1", SQL_NTS, - NULL, 0, NULL, 0, NULL, 0); - - // Test of case #2 - result = SQLForeignKeys(hstmt1, "", SQL_NTS, "", SQL_NTS, NULL, 0, - NULL, 0, NULL, 0, "ar_register", SQL_NTS); - - - // Test of case #3 - result = SQLForeignKeys(hstmt1, NULL, 0, NULL, 0, "employee", SQL_NTS, - NULL, 0, NULL, 0, "invoice", SQL_NTS); - - qlog("SQLForeignKeys result = %d\n", result); - - result = SQLNumResultCols(hstmt1, &cols); - qlog("cols SQLTables result = %d\n", result); - - result = SQLBindCol(hstmt1, 3, SQL_C_CHAR, pktab, sizeof(pktab), &pktab_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 4, SQL_C_CHAR, pkcol, sizeof(pkcol), &pkcol_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 7, SQL_C_CHAR, fktab, sizeof(fktab), &fktab_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 8, SQL_C_CHAR, fkcol, sizeof(fkcol), &fkcol_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 9, SQL_C_SHORT, &seq, 0, NULL); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 10, SQL_C_LONG, &update_rule, 0, &ur_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 11, SQL_C_LONG, &delete_rule, 0, &dr_len); - qlog("bind result = %d\n", result); - - result = SQLBindCol(hstmt1, 14, SQL_C_CHAR, tgname, sizeof(tgname), &tgname_len); - qlog("bind result = %d\n", result); - - result = SQLFetch(hstmt1); - qlog("SQLFetch result = %d\n", result); - while (result != SQL_NO_DATA_FOUND) { - qlog("fetch on stmt1: result = %d, pktab='%s', pkcol='%s', fktab='%s', fkcol='%s', seq=%d, update_rule=%d, ur_len=%d, delete_rule=%d, dr_len=%d, tgname='%s', tgname_len=%d\n", - result, pktab, pkcol, fktab, fkcol, seq, update_rule, ur_len, delete_rule, dr_len, tgname, tgname_len); - - result = SQLFetch(hstmt1); - } - qlog("SQLFetch result = %d\n", result); - - SQLFreeStmt(hstmt1, SQL_DROP); - -} -*/ - - - diff --git a/src/interfaces/odbc/connection.h b/src/interfaces/odbc/connection.h index 3340e15cbe..aaf0fffebb 100644 --- a/src/interfaces/odbc/connection.h +++ b/src/interfaces/odbc/connection.h @@ -35,33 +35,34 @@ typedef enum { } CONN_Status; /* These errors have general sql error state */ -#define CONNECTION_SERVER_NOT_REACHED 1 -#define CONNECTION_MSG_TOO_LONG 3 -#define CONNECTION_COULD_NOT_SEND 4 -#define CONNECTION_NO_SUCH_DATABASE 5 -#define CONNECTION_BACKEND_CRAZY 6 -#define CONNECTION_NO_RESPONSE 7 -#define CONNECTION_SERVER_REPORTED_ERROR 8 -#define CONNECTION_COULD_NOT_RECEIVE 9 -#define CONNECTION_SERVER_REPORTED_WARNING 10 -#define CONNECTION_NEED_PASSWORD 12 +#define CONNECTION_SERVER_NOT_REACHED 101 +#define CONNECTION_MSG_TOO_LONG 103 +#define CONNECTION_COULD_NOT_SEND 104 +#define CONNECTION_NO_SUCH_DATABASE 105 +#define CONNECTION_BACKEND_CRAZY 106 +#define CONNECTION_NO_RESPONSE 107 +#define CONNECTION_SERVER_REPORTED_ERROR 108 +#define CONNECTION_COULD_NOT_RECEIVE 109 +#define CONNECTION_SERVER_REPORTED_WARNING 110 +#define CONNECTION_NEED_PASSWORD 112 /* These errors correspond to specific SQL states */ -#define CONN_INIREAD_ERROR 1 -#define CONN_OPENDB_ERROR 2 -#define CONN_STMT_ALLOC_ERROR 3 -#define CONN_IN_USE 4 -#define CONN_UNSUPPORTED_OPTION 5 +#define CONN_INIREAD_ERROR 201 +#define CONN_OPENDB_ERROR 202 +#define CONN_STMT_ALLOC_ERROR 203 +#define CONN_IN_USE 204 +#define CONN_UNSUPPORTED_OPTION 205 /* Used by SetConnectoption to indicate unsupported options */ -#define CONN_INVALID_ARGUMENT_NO 6 +#define CONN_INVALID_ARGUMENT_NO 206 /* SetConnectOption: corresponds to ODBC--"S1009" */ -#define CONN_TRANSACT_IN_PROGRES 7 -#define CONN_NO_MEMORY_ERROR 8 -#define CONN_NOT_IMPLEMENTED_ERROR 9 -#define CONN_INVALID_AUTHENTICATION 10 -#define CONN_AUTH_TYPE_UNSUPPORTED 11 -#define CONN_UNABLE_TO_LOAD_DLL 12 +#define CONN_TRANSACT_IN_PROGRES 207 +#define CONN_NO_MEMORY_ERROR 208 +#define CONN_NOT_IMPLEMENTED_ERROR 209 +#define CONN_INVALID_AUTHENTICATION 210 +#define CONN_AUTH_TYPE_UNSUPPORTED 211 +#define CONN_UNABLE_TO_LOAD_DLL 212 +#define CONN_OPTION_VALUE_CHANGED 213 /* Conn_status defines */ #define CONN_IN_AUTOCOMMIT 0x01 @@ -200,6 +201,7 @@ typedef BOOL (FAR WINAPI *DriverToDataSourceProc) (UDWORD, /******* The Connection handle ************/ struct ConnectionClass_ { HENV henv; /* environment this connection was created on */ + StatementOptions stmtOptions; char *errormsg; int errornumber; CONN_Status status; @@ -244,7 +246,7 @@ char CC_connect(ConnectionClass *self, char do_password); char CC_add_statement(ConnectionClass *self, StatementClass *stmt); char CC_remove_statement(ConnectionClass *self, StatementClass *stmt); char CC_get_error(ConnectionClass *self, int *number, char **message); -QResultClass *CC_send_query(ConnectionClass *self, char *query, QResultClass *result_in, char *cursor); +QResultClass *CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi); void CC_clear_error(ConnectionClass *self); char *CC_create_errormsg(ConnectionClass *self); int CC_send_function(ConnectionClass *conn, int fnid, void *result_buf, int *actual_result_len, int result_is_int, LO_ARG *argv, int nargs); diff --git a/src/interfaces/odbc/convert.c b/src/interfaces/odbc/convert.c index a5b3cbfd3f..73e92e16c0 100644 --- a/src/interfaces/odbc/convert.c +++ b/src/interfaces/odbc/convert.c @@ -101,18 +101,38 @@ copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *val BindInfoClass *bic = &(stmt->bindings[col]); return copy_and_convert_field(stmt, field_type, value, (Int2)bic->returntype, (PTR)bic->buffer, - (SDWORD)bic->buflen, (SDWORD *)bic->used, FALSE); + (SDWORD)bic->buflen, (SDWORD *)bic->used); } /* This is called by SQLGetData() */ int copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType, - PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue, char multiple) + PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue) { -Int4 len = 0; +Int4 len = 0, copy_len = 0; SIMPLE_TIME st; time_t t = time(NULL); struct tm *tim; +int pcbValueOffset, rgbValueOffset; +char *rgbValueBindRow, *ptr; +int bind_row = stmt->bind_row; +int bind_size = stmt->options.bind_size; +int result = COPY_OK; +char tempBuf[TEXT_FIELD_SIZE+5]; + +/* rgbValueOffset is *ONLY* for character and binary data */ +/* pcbValueOffset is for computing any pcbValue location */ + + if (bind_size > 0) { + + pcbValueOffset = rgbValueOffset = (bind_size * bind_row); + } + else { + + pcbValueOffset = bind_row * sizeof(SDWORD); + rgbValueOffset = bind_row * cbValueMax; + + } memset(&st, 0, sizeof(SIMPLE_TIME)); @@ -122,13 +142,13 @@ struct tm *tim; st.d = tim->tm_mday; st.y = tim->tm_year + 1900; - mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, value, cbValueMax); + mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, (value==NULL)?"":value, cbValueMax); if ( ! value) { /* handle a null just by returning SQL_NULL_DATA in pcbValue, */ /* and doing nothing to the buffer. */ if(pcbValue) { - *pcbValue = SQL_NULL_DATA; + *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = SQL_NULL_DATA; } return COPY_OK; } @@ -191,7 +211,7 @@ struct tm *tim; /* This is for internal use by SQLStatistics() */ case PG_TYPE_INT28: { // this is an array of eight integers - short *short_array = (short *)rgbValue; + short *short_array = (short *) ( (char *) rgbValue + rgbValueOffset); len = 16; @@ -207,7 +227,7 @@ struct tm *tim; /* There is no corresponding fCType for this. */ if(pcbValue) - *pcbValue = len; + *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = len; return COPY_OK; /* dont go any further or the data will be trashed */ } @@ -215,12 +235,12 @@ struct tm *tim; /* This is a large object OID, which is used to store LONGVARBINARY objects. */ case PG_TYPE_LO: - return convert_lo( stmt, value, fCType, rgbValue, cbValueMax, pcbValue, multiple); + return convert_lo( stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset)); default: if (field_type == stmt->hdbc->lobj_type) /* hack until permanent type available */ - return convert_lo( stmt, value, fCType, rgbValue, cbValueMax, pcbValue, multiple); + return convert_lo( stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset)); } /* Change default into something useable */ @@ -231,21 +251,24 @@ struct tm *tim; } + rgbValueBindRow = (char *) rgbValue + rgbValueOffset; + if(fCType == SQL_C_CHAR) { + /* Special character formatting as required */ /* These really should return error if cbValueMax is not big enough. */ switch(field_type) { case PG_TYPE_DATE: len = 10; if (cbValueMax > len) - sprintf((char *)rgbValue, "%.4d-%.2d-%.2d", st.y, st.m, st.d); + sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d", st.y, st.m, st.d); break; case PG_TYPE_TIME: len = 8; if (cbValueMax > len) - sprintf((char *)rgbValue, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss); + sprintf(rgbValueBindRow, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss); break; case PG_TYPE_ABSTIME: @@ -253,15 +276,15 @@ struct tm *tim; case PG_TYPE_TIMESTAMP: len = 19; if (cbValueMax > len) - sprintf((char *) rgbValue, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", + sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", st.y, st.m, st.d, st.hh, st.mm, st.ss); break; case PG_TYPE_BOOL: len = 1; if (cbValueMax > len) { - strcpy((char *) rgbValue, value); - mylog("PG_TYPE_BOOL: rgbValue = '%s'\n", rgbValue); + strcpy(rgbValueBindRow, value); + mylog("PG_TYPE_BOOL: rgbValueBindRow = '%s'\n", rgbValueBindRow); } break; @@ -276,15 +299,48 @@ struct tm *tim; object used to store those. */ case PG_TYPE_BYTEA: // convert binary data to hex strings (i.e, 255 = "FF") - len = convert_pgbinary_to_char(value, rgbValue, cbValueMax); + len = convert_pgbinary_to_char(value, rgbValueBindRow, cbValueMax); + + /***** THIS IS NOT PROPERLY IMPLEMENTED *****/ break; default: /* convert linefeeds to carriage-return/linefeed */ - convert_linefeeds( (char *) value, rgbValue, cbValueMax); - len = strlen(rgbValue); + len = convert_linefeeds(value, tempBuf, sizeof(tempBuf)); + ptr = tempBuf; - mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValue = '%s'\n", len, cbValueMax, rgbValue); + mylog("DEFAULT: len = %d, ptr = '%s'\n", len, ptr); + + if (stmt->current_col >= 0) { + if (stmt->bindings[stmt->current_col].data_left == 0) + return COPY_NO_DATA_FOUND; + else if (stmt->bindings[stmt->current_col].data_left > 0) { + ptr += len - stmt->bindings[stmt->current_col].data_left; + len = stmt->bindings[stmt->current_col].data_left; + } + else + stmt->bindings[stmt->current_col].data_left = strlen(value); + } + + if (cbValueMax > 0) { + + copy_len = (len >= cbValueMax) ? cbValueMax -1 : len; + + /* Copy the data */ + strncpy_null(rgbValueBindRow, ptr, copy_len + 1); + + /* Adjust data_left for next time */ + if (stmt->current_col >= 0) { + stmt->bindings[stmt->current_col].data_left -= copy_len; + } + } + + /* Finally, check for truncation so that proper status can be returned */ + if ( len >= cbValueMax) + result = COPY_RESULT_TRUNCATED; + + + mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValueBindRow = '%s'\n", len, cbValueMax, rgbValueBindRow); break; } @@ -301,7 +357,13 @@ struct tm *tim; case SQL_C_DATE: len = 6; { - DATE_STRUCT *ds = (DATE_STRUCT *) rgbValue; + DATE_STRUCT *ds; + + if (bind_size > 0) { + ds = (DATE_STRUCT *) ((char *) rgbValue + (bind_row * bind_size)); + } else { + ds = (DATE_STRUCT *) rgbValue + bind_row; + } ds->year = st.y; ds->month = st.m; ds->day = st.d; @@ -311,7 +373,13 @@ struct tm *tim; case SQL_C_TIME: len = 6; { - TIME_STRUCT *ts = (TIME_STRUCT *) rgbValue; + TIME_STRUCT *ts; + + if (bind_size > 0) { + ts = (TIME_STRUCT *) ((char *) rgbValue + (bind_row * bind_size)); + } else { + ts = (TIME_STRUCT *) rgbValue + bind_row; + } ts->hour = st.hh; ts->minute = st.mm; ts->second = st.ss; @@ -321,7 +389,12 @@ struct tm *tim; case SQL_C_TIMESTAMP: len = 16; { - TIMESTAMP_STRUCT *ts = (TIMESTAMP_STRUCT *) rgbValue; + TIMESTAMP_STRUCT *ts; + if (bind_size > 0) { + ts = (TIMESTAMP_STRUCT *) ((char *) rgbValue + (bind_row * bind_size)); + } else { + ts = (TIMESTAMP_STRUCT *) rgbValue + bind_row; + } ts->year = st.y; ts->month = st.m; ts->day = st.d; @@ -334,59 +407,132 @@ struct tm *tim; case SQL_C_BIT: len = 1; - *((UCHAR *)rgbValue) = atoi(value); - mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n", atoi(value), cbValueMax, *((UCHAR *)rgbValue)); + if (bind_size > 0) { + *(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value); + } else { + *((UCHAR *)rgbValue + bind_row) = atoi(value); + } + // mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n", atoi(value), cbValueMax, *((UCHAR *)rgbValue)); break; case SQL_C_STINYINT: case SQL_C_TINYINT: len = 1; - *((SCHAR *) rgbValue) = atoi(value); + if (bind_size > 0) { + *(SCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value); + } else { + *((SCHAR *) rgbValue + bind_row) = atoi(value); + } break; case SQL_C_UTINYINT: len = 1; - *((UCHAR *) rgbValue) = atoi(value); + if (bind_size > 0) { + *(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value); + } else { + *((UCHAR *) rgbValue + bind_row) = atoi(value); + } break; case SQL_C_FLOAT: len = 4; - *((SFLOAT *)rgbValue) = (float) atof(value); + if (bind_size > 0) { + *(SFLOAT *) ((char *) rgbValue + (bind_row * bind_size)) = (float) atof(value); + } else { + *((SFLOAT *)rgbValue + bind_row) = (float) atof(value); + } break; case SQL_C_DOUBLE: len = 8; - *((SDOUBLE *)rgbValue) = atof(value); + if (bind_size > 0) { + *(SDOUBLE *) ((char *) rgbValue + (bind_row * bind_size)) = atof(value); + } else { + *((SDOUBLE *)rgbValue + bind_row) = atof(value); + } break; case SQL_C_SSHORT: case SQL_C_SHORT: len = 2; - *((SWORD *)rgbValue) = atoi(value); + if (bind_size > 0) { + *(SWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value); + } else { + *((SWORD *)rgbValue + bind_row) = atoi(value); + } break; case SQL_C_USHORT: len = 2; - *((UWORD *)rgbValue) = atoi(value); + if (bind_size > 0) { + *(UWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value); + } else { + *((UWORD *)rgbValue + bind_row) = atoi(value); + } break; case SQL_C_SLONG: case SQL_C_LONG: len = 4; - *((SDWORD *)rgbValue) = atol(value); + if (bind_size > 0) { + *(SDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(value); + } else { + *((SDWORD *)rgbValue + bind_row) = atol(value); + } break; case SQL_C_ULONG: len = 4; - *((UDWORD *)rgbValue) = atol(value); + if (bind_size > 0) { + *(UDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(value); + } else { + *((UDWORD *)rgbValue + bind_row) = atol(value); + } break; case SQL_C_BINARY: // truncate if necessary // convert octal escapes to bytes - len = convert_from_pgbinary(value, rgbValue, cbValueMax); - mylog("SQL_C_BINARY: len = %d\n", len); + + len = convert_from_pgbinary(value, tempBuf, sizeof(tempBuf)); + ptr = tempBuf; + + if (stmt->current_col >= 0) { + + /* No more data left for this column */ + if (stmt->bindings[stmt->current_col].data_left == 0) + return COPY_NO_DATA_FOUND; + + /* Second (or more) call to SQLGetData so move the pointer */ + else if (stmt->bindings[stmt->current_col].data_left > 0) { + ptr += len - stmt->bindings[stmt->current_col].data_left; + len = stmt->bindings[stmt->current_col].data_left; + } + + /* First call to SQLGetData so initialize data_left */ + else + stmt->bindings[stmt->current_col].data_left = len; + + } + + if (cbValueMax > 0) { + copy_len = (len > cbValueMax) ? cbValueMax : len; + + /* Copy the data */ + memcpy(rgbValueBindRow, ptr, copy_len); + + /* Adjust data_left for next time */ + if (stmt->current_col >= 0) { + stmt->bindings[stmt->current_col].data_left -= copy_len; + } + } + + /* Finally, check for truncation so that proper status can be returned */ + if ( len > cbValueMax) + result = COPY_RESULT_TRUNCATED; + + mylog("SQL_C_BINARY: len = %d, copy_len = %d\n", len, copy_len); break; default: @@ -395,10 +541,11 @@ struct tm *tim; } // store the length of what was copied, if there's a place for it - if(pcbValue) - *pcbValue = len; + if(pcbValue) { + *(SDWORD *) ((char *)pcbValue + pcbValueOffset) = len; + } - return COPY_OK; + return result; } @@ -423,6 +570,8 @@ struct tm *tim; SDWORD used; char *buffer, *buf; char in_quote = FALSE; +Oid lobj_oid; +int lobj_fd, retval; if ( ! old_statement) { @@ -731,18 +880,50 @@ char in_quote = FALSE; case SQL_VARBINARY: /* non-ascii characters should be converted to octal */ new_statement[npos++] = '\''; /* Open Quote */ - mylog("SQL_LONGVARBINARY: about to call convert_to_pgbinary, used = %d\n", used); + mylog("SQL_VARBINARY: about to call convert_to_pgbinary, used = %d\n", used); npos += convert_to_pgbinary(buf, &new_statement[npos], used); new_statement[npos++] = '\''; /* Close Quote */ break; + case SQL_LONGVARBINARY: + + if ( stmt->parameters[param_number].data_at_exec) { + + lobj_oid = stmt->parameters[param_number].lobj_oid; + + } + else { + + /* store the oid */ + lobj_oid = lo_creat(stmt->hdbc, INV_READ | INV_WRITE); + if (lobj_oid == 0) { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt create (in-line) large object."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* store the fd */ + lobj_fd = lo_open(stmt->hdbc, lobj_oid, INV_WRITE); + if ( lobj_fd < 0) { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt open (in-line) large object for writing."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + retval = lo_write(stmt->hdbc, lobj_fd, buffer, used); + + lo_close(stmt->hdbc, lobj_fd); + } + /* the oid of the large object -- just put that in for the parameter marker -- the data has already been sent to the large object */ - sprintf(param_string, "%d", stmt->parameters[param_number].lobj_oid); + sprintf(param_string, "%d", lobj_oid); strcpy(&new_statement[npos], param_string); npos += strlen(param_string); @@ -912,38 +1093,27 @@ int nf; } /* Change linefeed to carriage-return/linefeed */ -char * +int convert_linefeeds(char *si, char *dst, size_t max) { size_t i = 0, out = 0; -static char sout[TEXT_FIELD_SIZE+5]; -char *p; - if (dst) - p = dst; - else { - p = sout; - max = sizeof(sout); - } - - p[0] = '\0'; - - for (i = 0; i < strlen(si) && out < max; i++) { + for (i = 0; i < strlen(si) && out < max - 1; i++) { if (si[i] == '\n') { /* Only add the carriage-return if needed */ if (i > 0 && si[i-1] == '\r') { - p[out++] = si[i]; + dst[out++] = si[i]; continue; } - p[out++] = '\r'; - p[out++] = '\n'; + dst[out++] = '\r'; + dst[out++] = '\n'; } else - p[out++] = si[i]; + dst[out++] = si[i]; } - p[out] = '\0'; - return p; + dst[out] = '\0'; + return out; } /* Change carriage-return/linefeed to just linefeed @@ -1029,6 +1199,7 @@ convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValue { size_t i; int o=0; + for (i = 0; i < strlen(value); ) { if (value[i] == '\\') { @@ -1042,7 +1213,7 @@ int o=0; o++; } - rgbValue[o] = '\0'; + rgbValue[o] = '\0'; // extra protection return o; } @@ -1075,12 +1246,13 @@ int i, o=0; for (i = 0; i < len; i++) { mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]); - if (in[i] < 32 || in[i] > 126) { + if ( isalnum(in[i]) || in[i] == ' ') { + out[o++] = in[i]; + } + else { strcpy(&out[o], conv_to_octal(in[i])); o += 5; } - else - out[o++] = in[i]; } @@ -1150,15 +1322,25 @@ unsigned int i, o = 0; */ int convert_lo(StatementClass *stmt, void *value, Int2 fCType, PTR rgbValue, - SDWORD cbValueMax, SDWORD *pcbValue, char multiple) + SDWORD cbValueMax, SDWORD *pcbValue) { Oid oid; -int retval; +int retval, result, left = -1; +int bind_row = stmt->bind_row; +BindInfoClass *bindInfo = NULL; + + +/* If using SQLGetData, then current_col will be set */ + if (stmt->current_col >= 0) { + bindInfo = &stmt->bindings[stmt->current_col]; + left = bindInfo->data_left; + } /* if this is the first call for this column, open the large object for reading */ - if ( ! multiple) { + + if ( ! bindInfo || bindInfo->data_left == -1) { oid = atoi(value); stmt->lobj_fd = lo_open(stmt->hdbc, oid, INV_READ); if (stmt->lobj_fd < 0) { @@ -1166,6 +1348,22 @@ int retval; stmt->errormsg = "Couldnt open large object for reading."; return COPY_GENERAL_ERROR; } + + /* Get the size */ + retval = lo_lseek(stmt->hdbc, stmt->lobj_fd, 0L, SEEK_END); + if (retval >= 0) { + + left = lo_tell(stmt->hdbc, stmt->lobj_fd); + if (bindInfo) + bindInfo->data_left = left; + + /* return to beginning */ + lo_lseek(stmt->hdbc, stmt->lobj_fd, 0L, SEEK_SET); + } + } + + if (left == 0) { + return COPY_NO_DATA_FOUND; } if (stmt->lobj_fd < 0) { @@ -1174,7 +1372,7 @@ int retval; return COPY_GENERAL_ERROR; } - retval = lo_read(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValueMax); + retval = lo_read(stmt->hdbc, stmt->lobj_fd, (char *) rgbValue, cbValueMax); if (retval < 0) { lo_close(stmt->hdbc, stmt->lobj_fd); stmt->lobj_fd = -1; @@ -1183,20 +1381,26 @@ int retval; stmt->errormsg = "Error reading from large object."; return COPY_GENERAL_ERROR; } - else if (retval < cbValueMax) { /* success, all done */ + + if (retval < left) + result = COPY_RESULT_TRUNCATED; + else + result = COPY_OK; + + if (pcbValue) + *pcbValue = left < 0 ? SQL_NO_TOTAL : left; + + + if (bindInfo && bindInfo->data_left > 0) + bindInfo->data_left -= retval; + + + if (! bindInfo || bindInfo->data_left == 0) { lo_close(stmt->hdbc, stmt->lobj_fd); stmt->lobj_fd = -1; /* prevent further reading */ - - if (pcbValue) - *pcbValue = retval; - - return COPY_OK; } - else { /* retval == cbVaueMax -- assume truncated */ - if (pcbValue) - *pcbValue = SQL_NO_TOTAL; - return COPY_RESULT_TRUNCATED; - } + return result; + } diff --git a/src/interfaces/odbc/convert.h b/src/interfaces/odbc/convert.h index 8a75f647fa..7fd8254779 100644 --- a/src/interfaces/odbc/convert.h +++ b/src/interfaces/odbc/convert.h @@ -31,13 +31,13 @@ typedef struct { int copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *value, int col); int copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType, - PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue, char multiple); + PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue); int copy_statement_with_parameters(StatementClass *stmt); char *convert_escape(char *value); char *convert_money(char *s); char parse_datetime(char *buf, SIMPLE_TIME *st); -char *convert_linefeeds(char *s, char *dst, size_t max); +int convert_linefeeds(char *s, char *dst, size_t max); char *convert_special_chars(char *si, char *dst, int used); int convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax); @@ -46,6 +46,6 @@ int convert_to_pgbinary(unsigned char *in, char *out, int len); void encode(char *in, char *out); void decode(char *in, char *out); int convert_lo(StatementClass *stmt, void *value, Int2 fCType, PTR rgbValue, - SDWORD cbValueMax, SDWORD *pcbValue, char multiple); + SDWORD cbValueMax, SDWORD *pcbValue); #endif diff --git a/src/interfaces/odbc/dlg_specific.c b/src/interfaces/odbc/dlg_specific.c index 371b2d7ad8..ac83a2345f 100644 --- a/src/interfaces/odbc/dlg_specific.c +++ b/src/interfaces/odbc/dlg_specific.c @@ -112,6 +112,8 @@ int CALLBACK driver_optionsProc(HWND hdlg, CheckDlgButton(hdlg, DRV_PARSE, globals.parse); + CheckDlgButton(hdlg, DRV_CANCELASFREESTMT, globals.cancel_as_freestmt); + SetDlgItemInt(hdlg, DRV_CACHE_SIZE, globals.fetch_max, FALSE); SetDlgItemInt(hdlg, DRV_VARCHAR_SIZE, globals.max_varchar_size, FALSE); SetDlgItemInt(hdlg, DRV_LONGVARCHAR_SIZE, globals.max_longvarchar_size, TRUE); @@ -150,6 +152,8 @@ int CALLBACK driver_optionsProc(HWND hdlg, globals.parse = IsDlgButtonChecked(hdlg, DRV_PARSE); + globals.cancel_as_freestmt = IsDlgButtonChecked(hdlg, DRV_CANCELASFREESTMT); + globals.fetch_max = GetDlgItemInt(hdlg, DRV_CACHE_SIZE, NULL, FALSE); globals.max_varchar_size = GetDlgItemInt(hdlg, DRV_VARCHAR_SIZE, NULL, FALSE); globals.max_longvarchar_size= GetDlgItemInt(hdlg, DRV_LONGVARCHAR_SIZE, NULL, TRUE); // allows for SQL_NO_TOTAL @@ -176,6 +180,7 @@ int CALLBACK driver_optionsProc(HWND hdlg, CheckDlgButton(hdlg, DRV_USEDECLAREFETCH, DEFAULT_USEDECLAREFETCH); CheckDlgButton(hdlg, DRV_PARSE, DEFAULT_PARSE); + CheckDlgButton(hdlg, DRV_CANCELASFREESTMT, DEFAULT_CANCELASFREESTMT); /* Unknown Sizes */ CheckDlgButton(hdlg, DRV_UNKNOWN_DONTKNOW, 0); @@ -682,6 +687,14 @@ char temp[256]; else if ( ! override) globals.parse = DEFAULT_PARSE; + // SQLCancel calls SQLFreeStmt in Driver Manager + SQLGetPrivateProfileString(section, INI_CANCELASFREESTMT, "", + temp, sizeof(temp), filename); + if ( temp[0] ) + globals.cancel_as_freestmt = atoi(temp); + else if ( ! override) + globals.cancel_as_freestmt = DEFAULT_CANCELASFREESTMT; + // Readonly is stored in the driver section AND per datasource SQLGetPrivateProfileString(section, INI_READONLY, "", temp, sizeof(temp), filename); @@ -818,6 +831,10 @@ char tmp[128]; SQLWritePrivateProfileString(DBMS_NAME, INI_PARSE, tmp, ODBCINST_INI); + sprintf(tmp, "%d", globals.cancel_as_freestmt); + SQLWritePrivateProfileString(DBMS_NAME, + INI_CANCELASFREESTMT, tmp, ODBCINST_INI); + sprintf(tmp, "%d", globals.max_varchar_size); SQLWritePrivateProfileString(DBMS_NAME, INI_MAXVARCHARSIZE, tmp, ODBCINST_INI); diff --git a/src/interfaces/odbc/dlg_specific.h b/src/interfaces/odbc/dlg_specific.h index 829c9aeab7..02a7788996 100644 --- a/src/interfaces/odbc/dlg_specific.h +++ b/src/interfaces/odbc/dlg_specific.h @@ -64,6 +64,8 @@ #define INI_UNIQUEINDEX "UniqueIndex" /* Recognize unique indexes */ #define INI_UNKNOWNSIZES "UnknownSizes" /* How to handle unknown result set sizes */ +#define INI_CANCELASFREESTMT "CancelAsFreeStmt" + #define INI_USEDECLAREFETCH "UseDeclareFetch" /* Use Declare/Fetch cursors */ /* More ini stuff */ @@ -108,6 +110,8 @@ #define DEFAULT_LIE 0 #define DEFAULT_PARSE 0 +#define DEFAULT_CANCELASFREESTMT 0 + #define DEFAULT_EXTRASYSTABLEPREFIXES "dd_;" /* prototypes */ diff --git a/src/interfaces/odbc/environ.c b/src/interfaces/odbc/environ.c index f5c2798b5e..270524cf05 100644 --- a/src/interfaces/odbc/environ.c +++ b/src/interfaces/odbc/environ.c @@ -144,6 +144,14 @@ int status; strcpy(szSqlState, "S1000"); // general error break; + case STMT_ROW_OUT_OF_RANGE: + strcpy(szSqlState, "S1107"); + break; + + case STMT_OPERATION_CANCELLED: + strcpy(szSqlState, "S1008"); + break; + case STMT_NOT_IMPLEMENTED_ERROR: strcpy(szSqlState, "S1C00"); // == 'driver not capable' break; @@ -171,7 +179,15 @@ int status; case STMT_NO_CURSOR_NAME: strcpy(szSqlState, "S1015"); break; - default: + case STMT_INVALID_ARGUMENT_NO: + strcpy(szSqlState, "S1009"); + // invalid argument value + break; + case STMT_INVALID_CURSOR_POSITION: + strcpy(szSqlState, "S1109"); + break; + + default: strcpy(szSqlState, "S1000"); // also a general error break; @@ -218,6 +234,10 @@ int status; if (NULL != szSqlState) switch(status) { + case STMT_OPTION_VALUE_CHANGED: + case CONN_OPTION_VALUE_CHANGED: + strcpy(szSqlState, "01S02"); + break; case CONN_INIREAD_ERROR: strcpy(szSqlState, "IM002"); // data source not found @@ -254,6 +274,7 @@ int status; strcpy(szSqlState, "S1001"); break; case CONN_NOT_IMPLEMENTED_ERROR: + case STMT_NOT_IMPLEMENTED_ERROR: strcpy(szSqlState, "S1C00"); break; default: diff --git a/src/interfaces/odbc/execute.c b/src/interfaces/odbc/execute.c index d87859c9fc..cad5a5ca15 100644 --- a/src/interfaces/odbc/execute.c +++ b/src/interfaces/odbc/execute.c @@ -36,6 +36,8 @@ #include "bind.h" #include "lobj.h" +extern GLOBAL_VALUES globals; + // Perform a Prepare on the SQL statement RETCODE SQL_API SQLPrepare(HSTMT hstmt, @@ -342,7 +344,7 @@ int lf; mylog("SQLTransact: sending on conn %d '%s'\n", conn, stmt_string); - res = CC_send_query(conn, stmt_string, NULL, NULL); + res = CC_send_query(conn, stmt_string, NULL); CC_set_no_trans(conn); if ( ! res) { @@ -364,12 +366,14 @@ int lf; // - - - - - - - - - - RETCODE SQL_API SQLCancel( HSTMT hstmt) // Statement to cancel. { static char *func="SQLCancel"; StatementClass *stmt = (StatementClass *) hstmt; +RETCODE result; +HMODULE hmodule; +FARPROC addr; mylog( "%s: entering...\n", func); @@ -380,8 +384,35 @@ StatementClass *stmt = (StatementClass *) hstmt; } // Not in the middle of SQLParamData/SQLPutData so cancel like a close. - if (stmt->data_at_exec < 0) - return SQLFreeStmt(hstmt, SQL_CLOSE); + if (stmt->data_at_exec < 0) { + + + /* MAJOR HACK for Windows to reset the driver manager's cursor state: + Because of what seems like a bug in the Odbc driver manager, + SQLCancel does not act like a SQLFreeStmt(CLOSE), as many + applications depend on this behavior. So, this + brute force method calls the driver manager's function on + behalf of the application. + */ + +#ifdef WIN32 + if (globals.cancel_as_freestmt) { + hmodule = GetModuleHandle("ODBC32"); + addr = GetProcAddress(hmodule, "SQLFreeStmt"); + result = addr( (char *) (stmt->phstmt) - 96, SQL_CLOSE); + } + else { + result = SQLFreeStmt( hstmt, SQL_CLOSE); + } +#else + result = SQLFreeStmt( hstmt, SQL_CLOSE); +#endif + + mylog("SQLCancel: SQLFreeStmt returned %d\n", result); + + SC_clear_error(hstmt); + return SQL_SUCCESS; + } // In the middle of SQLParamData/SQLPutData, so cancel that. // Note, any previous data-at-exec buffers will be freed in the recycle diff --git a/src/interfaces/odbc/info.c b/src/interfaces/odbc/info.c index c8a1998ad8..49ec687002 100644 --- a/src/interfaces/odbc/info.c +++ b/src/interfaces/odbc/info.c @@ -71,7 +71,7 @@ ConnectionClass *conn = (ConnectionClass *) hdbc; ConnInfo *ci; char *p; - mylog( "%s: entering...\n", func); + mylog( "%s: entering...fInfoType=%d\n", func, fInfoType); if ( ! conn) { CC_log_error(func, "", NULL); @@ -224,7 +224,7 @@ char *p; break; case SQL_DBMS_NAME: /* ODBC 1.0 */ - if (pcbInfoValue) *pcbInfoValue = 10; + if (pcbInfoValue) *pcbInfoValue = strlen(DBMS_NAME); strncpy_null((char *)rgbInfoValue, DBMS_NAME, (size_t)cbInfoValueMax); break; @@ -238,7 +238,7 @@ char *p; // by direct experimentation they are not. postgres forces // the newer transaction to wait before doing something that // would cause one of these problems. - *((DWORD *)rgbInfoValue) = SQL_TXN_SERIALIZABLE; + *((DWORD *)rgbInfoValue) = SQL_TXN_READ_COMMITTED; //SQL_TXN_SERIALIZABLE; if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -269,11 +269,12 @@ char *p; case SQL_FETCH_DIRECTION: /* ODBC 1.0 */ // which fetch directions are supported? (bitmask) - *((DWORD *)rgbInfoValue) = globals.use_declarefetch ? 0 : (SQL_FD_FETCH_NEXT | + *((DWORD *)rgbInfoValue) = globals.use_declarefetch ? (SQL_FD_FETCH_NEXT) : (SQL_FD_FETCH_NEXT | SQL_FD_FETCH_FIRST | SQL_FD_FETCH_LAST | SQL_FD_FETCH_PRIOR | - SQL_FD_FETCH_ABSOLUTE); + SQL_FD_FETCH_ABSOLUTE | + SQL_FD_FETCH_RELATIVE); if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -285,7 +286,7 @@ char *p; case SQL_GETDATA_EXTENSIONS: /* ODBC 2.0 */ // (bitmask) - *((DWORD *)rgbInfoValue) = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND); + *((DWORD *)rgbInfoValue) = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND | SQL_GD_BLOCK); if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -296,8 +297,9 @@ char *p; break; case SQL_IDENTIFIER_CASE: /* ODBC 1.0 */ - // are identifiers case-sensitive (yes) - *((WORD *)rgbInfoValue) = SQL_IC_SENSITIVE; + // are identifiers case-sensitive (yes, but only when quoted. If not quoted, they + // default to lowercase) + *((WORD *)rgbInfoValue) = SQL_IC_LOWER; if(pcbInfoValue) { *pcbInfoValue = 2; } break; @@ -324,8 +326,7 @@ char *p; case SQL_LOCK_TYPES: /* ODBC 2.0 */ // which lock types does SQLSetPos support? (bitmask) - // SQLSetPos doesn't exist yet - *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_LCK_NO_CHANGE | SQL_LCK_EXCLUSIVE | SQL_LCK_UNLOCK) : 0; + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_LCK_NO_CHANGE | SQL_LCK_EXCLUSIVE | SQL_LCK_UNLOCK) : SQL_LCK_NO_CHANGE; if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -531,8 +532,7 @@ char *p; case SQL_POS_OPERATIONS: /* ODBC 2.0 */ // what functions does SQLSetPos support? (bitmask) - // SQLSetPos does not exist yet - *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_POS_POSITION | SQL_POS_REFRESH | SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD) : 0; + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_POS_POSITION | SQL_POS_REFRESH | SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD) : (SQL_POS_POSITION | SQL_POS_REFRESH); if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -585,8 +585,7 @@ char *p; break; case SQL_QUOTED_IDENTIFIER_CASE: /* ODBC 2.0 */ - // are "quoted" identifiers case-sensitive? - // well, we don't really let you quote identifiers, so... + // are "quoted" identifiers case-sensitive? YES *((WORD *)rgbInfoValue) = SQL_IC_SENSITIVE; if(pcbInfoValue) { *pcbInfoValue = 2; } break; @@ -713,7 +712,7 @@ char *p; case SQL_TXN_ISOLATION_OPTION: /* ODBC 1.0 */ // what transaction isolation options are available? (bitmask) // only the default--serializable transactions. - *((DWORD *)rgbInfoValue) = SQL_TXN_SERIALIZABLE; + *((DWORD *)rgbInfoValue) = SQL_TXN_READ_COMMITTED; // SQL_TXN_SERIALIZABLE; if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -751,7 +750,9 @@ static char *func = "SQLGetTypeInfo"; StatementClass *stmt = (StatementClass *) hstmt; TupleNode *row; int i; -Int4 type; +// Int4 type; +Int4 pgType; +Int2 sqlType; mylog("%s: entering...fSqlType = %d\n", func, fSqlType); @@ -760,6 +761,7 @@ Int4 type; return SQL_INVALID_HANDLE; } + stmt->manual_result = TRUE; stmt->result = QR_Constructor(); if( ! stmt->result) { @@ -786,25 +788,58 @@ Int4 type; QR_set_field_info(stmt->result, 13, "MINIMUM_SCALE", PG_TYPE_INT2, 2); QR_set_field_info(stmt->result, 14, "MAXIMUM_SCALE", PG_TYPE_INT2, 2); - // cycle through the types - for(i=0, type = pgtypes_defined[0]; type; type = pgtypes_defined[++i]) { - - if(fSqlType == SQL_ALL_TYPES || fSqlType == pgtype_to_sqltype(stmt, type)) { + for(i=0, sqlType = sqlTypes[0]; sqlType; sqlType = sqlTypes[++i]) { + pgType = sqltype_to_pgtype(sqlType); + if (fSqlType == SQL_ALL_TYPES || fSqlType == sqlType) { row = (TupleNode *)malloc(sizeof(TupleNode) + (15 - 1)*sizeof(TupleField)); /* These values can't be NULL */ + set_tuplefield_string(&row->tuple[0], pgtype_to_name(stmt, pgType)); + set_tuplefield_int2(&row->tuple[1], (Int2) sqlType); + set_tuplefield_int2(&row->tuple[6], pgtype_nullable(stmt, pgType)); + set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(stmt, pgType)); + set_tuplefield_int2(&row->tuple[8], pgtype_searchable(stmt, pgType)); + set_tuplefield_int2(&row->tuple[10], pgtype_money(stmt, pgType)); + + /* Localized data-source dependent data type name (always NULL) */ + set_tuplefield_null(&row->tuple[12]); + + /* These values can be NULL */ + set_nullfield_int4(&row->tuple[2], pgtype_precision(stmt, pgType, PG_STATIC, PG_STATIC)); + set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(stmt, pgType)); + set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(stmt, pgType)); + set_nullfield_string(&row->tuple[5], pgtype_create_params(stmt, pgType)); + set_nullfield_int2(&row->tuple[9], pgtype_unsigned(stmt, pgType)); + set_nullfield_int2(&row->tuple[11], pgtype_auto_increment(stmt, pgType)); + set_nullfield_int2(&row->tuple[13], pgtype_scale(stmt, pgType)); + set_nullfield_int2(&row->tuple[14], pgtype_scale(stmt, pgType)); + + QR_add_tuple(stmt->result, row); + } + } + + // cycle through the types +// for(i=0, type = pgtypes_defined[0]; type; type = pgtypes_defined[++i]) { + +// if(fSqlType == SQL_ALL_TYPES || fSqlType == pgtype_to_sqltype(stmt, type)) { + +// row = (TupleNode *)malloc(sizeof(TupleNode) + (15 - 1)*sizeof(TupleField)); + + /* These values can't be NULL */ +/* set_tuplefield_string(&row->tuple[0], pgtype_to_name(stmt, type)); set_tuplefield_int2(&row->tuple[1], pgtype_to_sqltype(stmt, type)); set_tuplefield_int2(&row->tuple[6], pgtype_nullable(stmt, type)); set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(stmt, type)); set_tuplefield_int2(&row->tuple[8], pgtype_searchable(stmt, type)); set_tuplefield_int2(&row->tuple[10], pgtype_money(stmt, type)); - +*/ /* Localized data-source dependent data type name (always NULL) */ - set_tuplefield_null(&row->tuple[12]); +// set_tuplefield_null(&row->tuple[12]); /* These values can be NULL */ +/* set_nullfield_int4(&row->tuple[2], pgtype_precision(stmt, type, PG_STATIC, PG_STATIC)); set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(stmt, type)); set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(stmt, type)); @@ -815,11 +850,13 @@ Int4 type; set_nullfield_int2(&row->tuple[14], pgtype_scale(stmt, type)); QR_add_tuple(stmt->result, row); - } - } +*/ +// } +// } stmt->status = STMT_FINISHED; stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; return SQL_SUCCESS; @@ -901,7 +938,7 @@ static char *func="SQLGetFunctions"; pfExists[SQL_API_SQLDATASOURCES] = FALSE; // only implemented by DM pfExists[SQL_API_SQLDESCRIBEPARAM] = FALSE; // not properly implemented pfExists[SQL_API_SQLDRIVERS] = FALSE; // only implemented by DM - pfExists[SQL_API_SQLEXTENDEDFETCH] = globals.use_declarefetch ? FALSE : TRUE; + pfExists[SQL_API_SQLEXTENDEDFETCH] = TRUE; pfExists[SQL_API_SQLFOREIGNKEYS] = TRUE; pfExists[SQL_API_SQLMORERESULTS] = TRUE; pfExists[SQL_API_SQLNATIVESQL] = TRUE; @@ -910,8 +947,8 @@ static char *func="SQLGetFunctions"; pfExists[SQL_API_SQLPRIMARYKEYS] = TRUE; pfExists[SQL_API_SQLPROCEDURECOLUMNS] = FALSE; pfExists[SQL_API_SQLPROCEDURES] = FALSE; - pfExists[SQL_API_SQLSETPOS] = FALSE; - pfExists[SQL_API_SQLSETSCROLLOPTIONS] = FALSE; // odbc 1.0 + pfExists[SQL_API_SQLSETPOS] = TRUE; + pfExists[SQL_API_SQLSETSCROLLOPTIONS] = TRUE; // odbc 1.0 pfExists[SQL_API_SQLTABLEPRIVILEGES] = FALSE; } } else { @@ -970,7 +1007,7 @@ static char *func="SQLGetFunctions"; case SQL_API_SQLDATASOURCES: *pfExists = FALSE; break; // only implemented by DM case SQL_API_SQLDESCRIBEPARAM: *pfExists = FALSE; break; // not properly implemented case SQL_API_SQLDRIVERS: *pfExists = FALSE; break; // only implemented by DM - case SQL_API_SQLEXTENDEDFETCH: *pfExists = globals.use_declarefetch ? FALSE : TRUE; break; + case SQL_API_SQLEXTENDEDFETCH: *pfExists = TRUE; break; case SQL_API_SQLFOREIGNKEYS: *pfExists = TRUE; break; case SQL_API_SQLMORERESULTS: *pfExists = TRUE; break; case SQL_API_SQLNATIVESQL: *pfExists = TRUE; break; @@ -979,8 +1016,8 @@ static char *func="SQLGetFunctions"; case SQL_API_SQLPRIMARYKEYS: *pfExists = TRUE; break; case SQL_API_SQLPROCEDURECOLUMNS: *pfExists = FALSE; break; case SQL_API_SQLPROCEDURES: *pfExists = FALSE; break; - case SQL_API_SQLSETPOS: *pfExists = FALSE; break; - case SQL_API_SQLSETSCROLLOPTIONS: *pfExists = FALSE; break; // odbc 1.0 + case SQL_API_SQLSETPOS: *pfExists = TRUE; break; + case SQL_API_SQLSETSCROLLOPTIONS: *pfExists = TRUE; break; // odbc 1.0 case SQL_API_SQLTABLEPRIVILEGES: *pfExists = FALSE; break; } } @@ -1253,6 +1290,7 @@ mylog("%s: entering...stmt=%u\n", func, stmt); // set up the current tuple pointer for SQLFetch stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; SQLFreeStmt(htbl_stmt, SQL_DROP); @@ -1600,6 +1638,7 @@ ConnInfo *ci; // set up the current tuple pointer for SQLFetch stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; SQLFreeStmt(hcol_stmt, SQL_DROP); @@ -1683,6 +1722,7 @@ mylog("%s: entering...stmt=%u\n", func, stmt); } stmt->status = STMT_FINISHED; stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; mylog("SQLSpecialColumns(): EXIT, stmt=%u\n", stmt); @@ -2002,6 +2042,7 @@ mylog("%s: entering...stmt=%u\n", func, stmt); // set up the current tuple pointer for SQLFetch stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; error = FALSE; @@ -2186,6 +2227,7 @@ Int2 result_cols; // set up the current tuple pointer for SQLFetch stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; mylog("SQLPrimaryKeys(): EXIT, stmt=%u\n", stmt); @@ -2270,6 +2312,7 @@ Int2 result_cols; // set up the current tuple pointer for SQLFetch stmt->currTuple = -1; + stmt->rowset_start = -1; stmt->current_col = -1; diff --git a/src/interfaces/odbc/misc.c b/src/interfaces/odbc/misc.c index 7e1501842c..60baaa63f2 100644 --- a/src/interfaces/odbc/misc.c +++ b/src/interfaces/odbc/misc.c @@ -44,13 +44,13 @@ generate_filename(char* dirname,char* prefix,char* filename) return; strcpy(filename,dirname); - strcat(filename,DIRSEPERATOR); + strcat(filename,DIRSEPARATOR); if(prefix != 0) strcat(filename,prefix); #ifndef WIN32 strcat(filename,ptr->pw_name); #endif - sprintf(filename,"%s%d%s",filename,pid,".log"); + sprintf(filename,"%s%u%s",filename,pid,".log"); return; } diff --git a/src/interfaces/odbc/misc.h b/src/interfaces/odbc/misc.h index 194a08161c..8eca682d5a 100644 --- a/src/interfaces/odbc/misc.h +++ b/src/interfaces/odbc/misc.h @@ -39,33 +39,41 @@ #ifdef MY_LOG -#define MYLOGFILE "mylog_" -#ifndef WIN32 -#define MYLOGDIR "/tmp" + #define MYLOGFILE "mylog_" + #ifndef WIN32 + #define MYLOGDIR "/tmp" + #else + #define MYLOGDIR "c:" + #endif + void mylog(); /* prototype */ #else -#define MYLOGDIR "c:" -#endif -void mylog(); /* prototype */ -#else -#define mylog // mylog + #ifndef WIN32 + #define mylog(args...) /* GNU convention for variable arguments */ + #else + #define mylog // mylog + #endif #endif #ifdef Q_LOG -#define QLOGFILE "psqlodbc_" -#ifndef WIN32 -#define QLOGDIR "/tmp" + #define QLOGFILE "psqlodbc_" + #ifndef WIN32 + #define QLOGDIR "/tmp" + #else + #define QLOGDIR "c:" + #endif + void qlog(); /* prototype */ #else -#define QLOGDIR "c:" -#endif -void qlog(); /* prototype */ -#else -#define qlog // qlog + #ifndef WIN32 + #define qlog(args...) /* GNU convention for variable arguments */ + #else + #define qlog // qlog + #endif #endif #ifndef WIN32 -#define DIRSEPERATOR "/" +#define DIRSEPARATOR "/" #else -#define DIRSEPERATOR "\\" +#define DIRSEPARATOR "\\" #endif void remove_newlines(char *string); diff --git a/src/interfaces/odbc/options.c b/src/interfaces/odbc/options.c index ec65364284..a263c98c2d 100644 --- a/src/interfaces/odbc/options.c +++ b/src/interfaces/odbc/options.c @@ -37,6 +37,219 @@ extern GLOBAL_VALUES globals; +RETCODE set_statement_option(ConnectionClass *conn, + StatementClass *stmt, + UWORD fOption, + UDWORD vParam) +{ +static char *func="set_statement_option"; +char changed = FALSE; + + + switch(fOption) { + case SQL_ASYNC_ENABLE:/* ignored */ + break; + + case SQL_BIND_TYPE: + /* now support multi-column and multi-row binding */ + if (conn) conn->stmtOptions.bind_size = vParam; + if (stmt) stmt->options.bind_size = vParam; + break; + + case SQL_CONCURRENCY: + /* positioned update isn't supported so cursor concurrency is read-only */ + + if (conn) conn->stmtOptions.scroll_concurrency = vParam; + if (stmt) stmt->options.scroll_concurrency = vParam; + break; + + /* + if (globals.lie) { + if (conn) conn->stmtOptions.scroll_concurrency = vParam; + if (stmt) stmt->options.scroll_concurrency = vParam; + } + else { + + if (conn) conn->stmtOptions.scroll_concurrency = SQL_CONCUR_READ_ONLY; + if (stmt) stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + + if (vParam != SQL_CONCUR_READ_ONLY) + changed = TRUE; + } + break; + */ + + case SQL_CURSOR_TYPE: + /* if declare/fetch, then type can only be forward. + otherwise, it can only be forward or static. + */ + mylog("SetStmtOption(): SQL_CURSOR_TYPE = %d\n", vParam); + + if (globals.lie) { + + if (conn) conn->stmtOptions.cursor_type = vParam; + if (stmt) stmt->options.cursor_type = vParam; + + } + else { + if (globals.use_declarefetch) { + + if (conn) conn->stmtOptions.cursor_type = SQL_CURSOR_FORWARD_ONLY; + if (stmt) stmt->options.cursor_type = SQL_CURSOR_FORWARD_ONLY; + + if (vParam != SQL_CURSOR_FORWARD_ONLY) + changed = TRUE; + } + else { + if (vParam == SQL_CURSOR_FORWARD_ONLY || vParam == SQL_CURSOR_STATIC) { + + if (conn) conn->stmtOptions.cursor_type = vParam; // valid type + if (stmt) stmt->options.cursor_type = vParam; // valid type + } + else { + + if (conn) conn->stmtOptions.cursor_type = SQL_CURSOR_STATIC; + if (stmt) stmt->options.cursor_type = SQL_CURSOR_STATIC; + + changed = TRUE; + } + } + } + break; + + case SQL_KEYSET_SIZE: /* ignored, but saved and returned */ + mylog("SetStmtOption(): SQL_KEYSET_SIZE, vParam = %d\n", vParam); + + if (conn) conn->stmtOptions.keyset_size = vParam; + if (stmt) stmt->options.keyset_size = vParam; + + break; + + /* + if (globals.lie) + stmt->keyset_size = vParam; + else { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Driver does not support keyset size option"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + */ + + case SQL_MAX_LENGTH:/* ignored, but saved */ + mylog("SetStmtOption(): SQL_MAX_LENGTH, vParam = %d\n", vParam); + if (conn) conn->stmtOptions.maxLength = vParam; + if (stmt) stmt->options.maxLength = vParam; + break; + + case SQL_MAX_ROWS: /* ignored, but saved */ + mylog("SetStmtOption(): SQL_MAX_ROWS, vParam = %d\n", vParam); + if (conn) conn->stmtOptions.maxRows = vParam; + if (stmt) stmt->options.maxRows = vParam; + break; + + case SQL_NOSCAN: /* ignored */ + mylog("SetStmtOption: SQL_NOSCAN, vParam = %d\n", vParam); + break; + + case SQL_QUERY_TIMEOUT: /* ignored */ + mylog("SetStmtOption: SQL_QUERY_TIMEOUT, vParam = %d\n", vParam); + // "0" returned in SQLGetStmtOption + break; + + case SQL_RETRIEVE_DATA: /* ignored, but saved */ + mylog("SetStmtOption(): SQL_RETRIEVE_DATA, vParam = %d\n", vParam); + if (conn) conn->stmtOptions.retrieve_data = vParam; + if (stmt) stmt->options.retrieve_data = vParam; + break; + + case SQL_ROWSET_SIZE: + mylog("SetStmtOption(): SQL_ROWSET_SIZE, vParam = %d\n", vParam); + + + /* Save old rowset size for SQLExtendedFetch purposes + If the rowset_size is being changed since the last call + to fetch rows. + */ + + if (stmt && stmt->save_rowset_size <= 0 && stmt->last_fetch_count > 0 ) + stmt->save_rowset_size = stmt->options.rowset_size; + + if (vParam < 1) { + vParam = 1; + changed = TRUE; + } + + if (conn) conn->stmtOptions.rowset_size = vParam; + if (stmt) stmt->options.rowset_size = vParam; + + + break; + + case SQL_SIMULATE_CURSOR: /* NOT SUPPORTED */ + if (stmt) { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; + SC_log_error(func, "", stmt); + } + if (conn) { + conn->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + conn->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; + CC_log_error(func, "", conn); + } + return SQL_ERROR; + + case SQL_USE_BOOKMARKS: /* NOT SUPPORTED */ + if (stmt) { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Driver does not support (SET) using bookmarks."; + SC_log_error(func, "", stmt); + } + if (conn) { + conn->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + conn->errormsg = "Driver does not support (SET) using bookmarks."; + CC_log_error(func, "", conn); + } + return SQL_ERROR; + + default: + { + char option[64]; + + if (stmt) { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Unknown statement option (Set)"; + sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); + SC_log_error(func, option, stmt); + } + if (conn) { + conn->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + conn->errormsg = "Unknown statement option (Set)"; + sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); + CC_log_error(func, option, conn); + } + + return SQL_ERROR; + } + } + + if (changed) { + if (stmt) { + stmt->errormsg = "Requested value changed."; + stmt->errornumber = STMT_OPTION_VALUE_CHANGED; + } + if (conn) { + conn->errormsg = "Requested value changed."; + conn->errornumber = STMT_OPTION_VALUE_CHANGED; + } + return SQL_SUCCESS_WITH_INFO; + } + else + return SQL_SUCCESS; +} + + + /* Implements only SQL_AUTOCOMMIT */ RETCODE SQL_API SQLSetConnectOption( HDBC hdbc, @@ -45,6 +258,9 @@ RETCODE SQL_API SQLSetConnectOption( { static char *func="SQLSetConnectOption"; ConnectionClass *conn = (ConnectionClass *) hdbc; +char changed = FALSE; +RETCODE retval; +int i; mylog("%s: entering...\n", func); @@ -55,6 +271,47 @@ ConnectionClass *conn = (ConnectionClass *) hdbc; switch (fOption) { + /* Statement Options -- + (apply to all stmts on the connection and become defaults for new stmts) + */ + case SQL_ASYNC_ENABLE: + case SQL_BIND_TYPE: + case SQL_CONCURRENCY: + case SQL_CURSOR_TYPE: + case SQL_KEYSET_SIZE: + case SQL_MAX_LENGTH: + case SQL_MAX_ROWS: + case SQL_NOSCAN: + case SQL_QUERY_TIMEOUT: + case SQL_RETRIEVE_DATA: + case SQL_ROWSET_SIZE: + case SQL_SIMULATE_CURSOR: + case SQL_USE_BOOKMARKS: + + /* Affect all current Statements */ + for (i = 0; i < conn->num_stmts; i++) { + if ( conn->stmts[i]) { + set_statement_option(NULL, conn->stmts[i], fOption, vParam); + } + } + + /* Become the default for all future statements on this connection */ + retval = set_statement_option(conn, NULL, fOption, vParam); + + if (retval == SQL_SUCCESS_WITH_INFO) + changed = TRUE; + else if (retval == SQL_ERROR) + return SQL_ERROR; + + break; + + /**********************************/ + /***** Connection Options *******/ + /**********************************/ + + case SQL_ACCESS_MODE: /* ignored */ + break; + case SQL_AUTOCOMMIT: /* Since we are almost always in a transaction, this is now ok. @@ -89,19 +346,34 @@ ConnectionClass *conn = (ConnectionClass *) hdbc; break; - case SQL_LOGIN_TIMEOUT: + case SQL_CURRENT_QUALIFIER: /* ignored */ break; - case SQL_ACCESS_MODE: + case SQL_LOGIN_TIMEOUT: /* ignored */ break; - case SQL_TXN_ISOLATION: + case SQL_PACKET_SIZE: /* ignored */ + break; + + case SQL_QUIET_MODE: /* ignored */ + break; + + case SQL_TXN_ISOLATION: /* ignored */ + break; + + /* These options should be handled by driver manager */ + case SQL_ODBC_CURSORS: + case SQL_OPT_TRACE: + case SQL_OPT_TRACEFILE: + case SQL_TRANSLATE_DLL: + case SQL_TRANSLATE_OPTION: + CC_log_error(func, "This connect option (Set) is only used by the Driver Manager", conn); break; default: { char option[64]; - conn->errormsg = "Driver does not support setting this connect option"; + conn->errormsg = "Unknown connect option (Set)"; conn->errornumber = CONN_UNSUPPORTED_OPTION; sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); CC_log_error(func, option, conn); @@ -109,7 +381,14 @@ ConnectionClass *conn = (ConnectionClass *) hdbc; } } - return SQL_SUCCESS; + + if (changed) { + conn->errornumber = CONN_OPTION_VALUE_CHANGED; + conn->errormsg = "Requested value changed."; + return SQL_SUCCESS_WITH_INFO; + } + else + return SQL_SUCCESS; } // - - - - - - - - - @@ -131,22 +410,50 @@ ConnectionClass *conn = (ConnectionClass *) hdbc; } switch (fOption) { + case SQL_ACCESS_MODE:/* NOT SUPPORTED */ + *((UDWORD *) pvParam) = SQL_MODE_READ_WRITE; + break; + case SQL_AUTOCOMMIT: *((UDWORD *)pvParam) = (UDWORD)( CC_is_in_autocommit(conn) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); break; - /* don't use qualifiers */ - case SQL_CURRENT_QUALIFIER: + case SQL_CURRENT_QUALIFIER: /* don't use qualifiers */ if(pvParam) strcpy(pvParam, ""); break; + case SQL_LOGIN_TIMEOUT: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = 0; + break; + + case SQL_PACKET_SIZE: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = globals.socket_buffersize; + break; + + case SQL_QUIET_MODE:/* NOT SUPPORTED */ + *((UDWORD *) pvParam) = (UDWORD) NULL; + break; + + case SQL_TXN_ISOLATION:/* NOT SUPPORTED */ + *((UDWORD *) pvParam) = SQL_TXN_SERIALIZABLE; + break; + + /* These options should be handled by driver manager */ + case SQL_ODBC_CURSORS: + case SQL_OPT_TRACE: + case SQL_OPT_TRACEFILE: + case SQL_TRANSLATE_DLL: + case SQL_TRANSLATE_OPTION: + CC_log_error(func, "This connect option (Get) is only used by the Driver Manager", conn); + break; + default: { char option[64]; - conn->errormsg = "Driver does not support getting this connect option"; + conn->errormsg = "Unknown connect option (Get)"; conn->errornumber = CONN_UNSUPPORTED_OPTION; sprintf(option, "fOption=%d", fOption); CC_log_error(func, option, conn); @@ -181,104 +488,7 @@ char changed = FALSE; return SQL_INVALID_HANDLE; } - switch(fOption) { - case SQL_QUERY_TIMEOUT: - mylog("SetStmtOption: vParam = %d\n", vParam); - // "0" returned in SQLGetStmtOption - break; - - case SQL_MAX_LENGTH: - // "4096" returned in SQLGetStmtOption - break; - - case SQL_MAX_ROWS: - mylog("SetStmtOption(): SQL_MAX_ROWS = %d, returning success\n", vParam); - stmt->maxRows = vParam; - return SQL_SUCCESS; - break; - - case SQL_ROWSET_SIZE: - mylog("SetStmtOption(): SQL_ROWSET_SIZE = %d\n", vParam); - - stmt->rowset_size = 1; // only support 1 row at a time - if (vParam != 1) - changed = TRUE; - - break; - - case SQL_KEYSET_SIZE: - mylog("SetStmtOption(): SQL_KEYSET_SIZE = %d\n", vParam); - if (globals.lie) - stmt->keyset_size = vParam; - else { - stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; - stmt->errormsg = "Driver does not support keyset size option"; - SC_log_error(func, "", stmt); - return SQL_ERROR; - } - break; - - case SQL_CONCURRENCY: - // positioned update isn't supported so cursor concurrency is read-only - mylog("SetStmtOption(): SQL_CONCURRENCY = %d\n", vParam); - - if (globals.lie) - stmt->scroll_concurrency = vParam; - else { - stmt->scroll_concurrency = SQL_CONCUR_READ_ONLY; - if (vParam != SQL_CONCUR_READ_ONLY) - changed = TRUE; - } - break; - - case SQL_CURSOR_TYPE: - // if declare/fetch, then type can only be forward. - // otherwise, it can only be forward or static. - mylog("SetStmtOption(): SQL_CURSOR_TYPE = %d\n", vParam); - - if (globals.lie) - stmt->cursor_type = vParam; - else { - if (globals.use_declarefetch) { - stmt->cursor_type = SQL_CURSOR_FORWARD_ONLY; - if (vParam != SQL_CURSOR_FORWARD_ONLY) - changed = TRUE; - } - else { - if (vParam == SQL_CURSOR_FORWARD_ONLY || vParam == SQL_CURSOR_STATIC) - stmt->cursor_type = vParam; // valid type - else { - stmt->cursor_type = SQL_CURSOR_STATIC; - changed = TRUE; - } - } - } - break; - - case SQL_SIMULATE_CURSOR: - stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; - stmt->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; - SC_log_error(func, "", stmt); - return SQL_ERROR; - - default: - { - char option[64]; - stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; - stmt->errormsg = "Driver does not support setting this statement option"; - sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); - SC_log_error(func, option, stmt); - return SQL_ERROR; - } - } - - if (changed) { - stmt->errormsg = "Requested value changed."; - stmt->errornumber = STMT_OPTION_VALUE_CHANGED; - return SQL_SUCCESS_WITH_INFO; - } - else - return SQL_SUCCESS; + return set_statement_option(NULL, stmt, fOption, vParam); } @@ -304,54 +514,78 @@ StatementClass *stmt = (StatementClass *) hstmt; } switch(fOption) { - case SQL_QUERY_TIMEOUT: - // how long we wait on a query before returning to the - // application (0 == forever) - *((SDWORD *)pvParam) = 0; + case SQL_GET_BOOKMARK:/* NOT SUPPORTED */ + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Driver does not support getting bookmarks."; + SC_log_error(func, "", stmt); + return SQL_ERROR; break; - case SQL_MAX_LENGTH: - // what is the maximum length that will be returned in - // a single column - *((SDWORD *)pvParam) = 4096; + case SQL_ROW_NUMBER: + *((SDWORD *) pvParam) = stmt->currTuple + 1; break; - case SQL_MAX_ROWS: - *((SDWORD *)pvParam) = stmt->maxRows; - mylog("GetSmtOption: MAX_ROWS, returning %d\n", stmt->maxRows); + case SQL_ASYNC_ENABLE: /* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_ASYNC_ENABLE_OFF; + break; + + case SQL_BIND_TYPE: + *((SDWORD *) pvParam) = stmt->options.bind_size; + break; + + case SQL_CONCURRENCY: /* NOT REALLY SUPPORTED */ + mylog("GetStmtOption(): SQL_CONCURRENCY\n"); + *((SDWORD *)pvParam) = stmt->options.scroll_concurrency; + break; + + case SQL_CURSOR_TYPE: /* PARTIAL SUPPORT */ + mylog("GetStmtOption(): SQL_CURSOR_TYPE\n"); + *((SDWORD *)pvParam) = stmt->options.cursor_type; + break; + + case SQL_KEYSET_SIZE: /* NOT SUPPORTED, but saved */ + mylog("GetStmtOption(): SQL_KEYSET_SIZE\n"); + *((SDWORD *)pvParam) = stmt->options.keyset_size; + break; + + case SQL_MAX_LENGTH: /* NOT SUPPORTED, but saved */ + *((SDWORD *)pvParam) = stmt->options.maxLength; + break; + + case SQL_MAX_ROWS: /* NOT SUPPORTED, but saved */ + *((SDWORD *)pvParam) = stmt->options.maxRows; + mylog("GetSmtOption: MAX_ROWS, returning %d\n", stmt->options.maxRows); + break; + + case SQL_NOSCAN:/* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_NOSCAN_ON; + break; + + case SQL_QUERY_TIMEOUT: /* NOT SUPPORTED */ + *((SDWORD *) pvParam) = 0; + break; + + case SQL_RETRIEVE_DATA: /* NOT SUPPORTED, but saved */ + *((SDWORD *) pvParam) = stmt->options.retrieve_data; break; case SQL_ROWSET_SIZE: - mylog("GetStmtOption(): SQL_ROWSET_SIZE\n"); - *((SDWORD *)pvParam) = stmt->rowset_size; + *((SDWORD *) pvParam) = stmt->options.rowset_size; break; - case SQL_KEYSET_SIZE: - mylog("GetStmtOption(): SQL_KEYSET_SIZE\n"); - *((SDWORD *)pvParam) = stmt->keyset_size; + case SQL_SIMULATE_CURSOR:/* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_SC_NON_UNIQUE; break; - case SQL_CONCURRENCY: - mylog("GetStmtOption(): SQL_CONCURRENCY\n"); - *((SDWORD *)pvParam) = stmt->scroll_concurrency; + case SQL_USE_BOOKMARKS:/* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_UB_OFF; break; - case SQL_CURSOR_TYPE: - mylog("GetStmtOption(): SQL_CURSOR_TYPE\n"); - *((SDWORD *)pvParam) = stmt->cursor_type; - break; - - case SQL_SIMULATE_CURSOR: - stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; - stmt->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; - SC_log_error(func, "", stmt); - return SQL_ERROR; - default: { char option[64]; stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; - stmt->errormsg = "Driver does not support getting this statement option"; + stmt->errormsg = "Unknown statement option (Get)"; sprintf(option, "fOption=%d", fOption); SC_log_error(func, option, stmt); return SQL_ERROR; diff --git a/src/interfaces/odbc/pgtypes.c b/src/interfaces/odbc/pgtypes.c index cdd9fc2654..169f9346ca 100644 --- a/src/interfaces/odbc/pgtypes.c +++ b/src/interfaces/odbc/pgtypes.c @@ -73,7 +73,104 @@ Int4 pgtypes_defined[] = { PG_TYPE_BYTEA, PG_TYPE_LO, 0 }; - + +/* These are the SQL Types reported in SQLGetTypeInfo. */ +Int2 sqlTypes [] = { + SQL_BIGINT, + /* SQL_BINARY, */ + SQL_BIT, + SQL_CHAR, + SQL_DATE, + SQL_DECIMAL, + SQL_DOUBLE, + SQL_FLOAT, + SQL_INTEGER, + SQL_LONGVARBINARY, + SQL_LONGVARCHAR, + SQL_NUMERIC, + SQL_REAL, + SQL_SMALLINT, + SQL_TIME, + SQL_TIMESTAMP, + SQL_TINYINT, + SQL_VARBINARY, + SQL_VARCHAR, + 0 +}; + +Int4 sqltype_to_pgtype(SWORD fSqlType) +{ +Int4 pgType; + + switch(fSqlType) { + + case SQL_BINARY: + pgType = PG_TYPE_BYTEA; + break; + + case SQL_CHAR: + pgType = PG_TYPE_BPCHAR; + break; + + case SQL_BIT: + pgType = globals.bools_as_char ? PG_TYPE_CHAR : PG_TYPE_BOOL; + break; + + case SQL_DATE: + pgType = PG_TYPE_DATE; + break; + + case SQL_DOUBLE: + case SQL_FLOAT: + pgType = PG_TYPE_FLOAT8; + break; + + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_NUMERIC: + case SQL_DECIMAL: + pgType = PG_TYPE_INT4; + break; + + case SQL_LONGVARBINARY: + pgType = PG_TYPE_LO; + break; + + case SQL_LONGVARCHAR: + pgType = globals.text_as_longvarchar ? PG_TYPE_TEXT : PG_TYPE_VARCHAR; + break; + + case SQL_REAL: + pgType = PG_TYPE_FLOAT4; + break; + + case SQL_SMALLINT: + case SQL_TINYINT: + pgType = PG_TYPE_INT2; + break; + + case SQL_TIME: + pgType = PG_TYPE_TIME; + break; + + case SQL_TIMESTAMP: + pgType = PG_TYPE_DATETIME; + break; + + case SQL_VARBINARY: + pgType = PG_TYPE_BYTEA; + break; + + case SQL_VARCHAR: + pgType = PG_TYPE_VARCHAR; + break; + + default: + break; + } + + return pgType; +} /* There are two ways of calling this function: 1. When going through the supported PG types (SQLGetTypeInfo) @@ -94,7 +191,7 @@ Int2 pgtype_to_sqltype(StatementClass *stmt, Int4 type) case PG_TYPE_CHAR16: case PG_TYPE_NAME: return SQL_CHAR; - case PG_TYPE_BPCHAR: return SQL_CHAR; // temporary? + case PG_TYPE_BPCHAR: return SQL_CHAR; case PG_TYPE_VARCHAR: return SQL_VARCHAR; @@ -165,7 +262,7 @@ char *pgtype_to_name(StatementClass *stmt, Int4 type) case PG_TYPE_CHAR8: return "char8"; case PG_TYPE_CHAR16: return "char16"; case PG_TYPE_VARCHAR: return "varchar"; - case PG_TYPE_BPCHAR: return "bpchar"; + case PG_TYPE_BPCHAR: return "char"; case PG_TYPE_TEXT: return "text"; case PG_TYPE_NAME: return "name"; case PG_TYPE_INT2: return "int2"; diff --git a/src/interfaces/odbc/pgtypes.h b/src/interfaces/odbc/pgtypes.h index e83ec040ab..b1da5aa253 100644 --- a/src/interfaces/odbc/pgtypes.h +++ b/src/interfaces/odbc/pgtypes.h @@ -61,10 +61,12 @@ #define PG_TYPE_TIMESTAMP 1296 extern Int4 pgtypes_defined[]; +extern Int2 sqlTypes[]; /* Defines for pgtype_precision */ #define PG_STATIC -1 +Int4 sqltype_to_pgtype(Int2 fSqlType); Int2 pgtype_to_sqltype(StatementClass *stmt, Int4 type); Int2 pgtype_to_ctype(StatementClass *stmt, Int4 type); diff --git a/src/interfaces/odbc/psqlodbc.c b/src/interfaces/odbc/psqlodbc.c index 5a3527c759..0df56ee1e7 100644 --- a/src/interfaces/odbc/psqlodbc.c +++ b/src/interfaces/odbc/psqlodbc.c @@ -126,4 +126,3 @@ RETCODE SQL_API SQLDummyOrdinal(void) return SQL_SUCCESS; } - diff --git a/src/interfaces/odbc/psqlodbc.h b/src/interfaces/odbc/psqlodbc.h index d00c67dd82..b0356b1b05 100644 --- a/src/interfaces/odbc/psqlodbc.h +++ b/src/interfaces/odbc/psqlodbc.h @@ -40,7 +40,7 @@ typedef UInt4 Oid; #define MAX_CONNECT_STRING 4096 #define ERROR_MSG_LENGTH 4096 #define FETCH_MAX 100 /* default number of rows to cache for declare/fetch */ -#define FETCH_INCR 1000 +#define TUPLE_MALLOC_INC 100 #define SOCK_BUFFER_SIZE 4096 /* default socket buffer size */ #define MAX_CONNECTIONS 128 /* conns per environment (arbitrary) */ #define MAX_FIELDS 512 @@ -70,8 +70,8 @@ typedef UInt4 Oid; /* Driver stuff */ #define DRIVERNAME "PostgreSQL ODBC" #define DBMS_NAME "PostgreSQL" -#define DBMS_VERSION "06.40.0001 PostgreSQL 6.4" -#define POSTGRESDRIVERVERSION "06.40.0001" +#define DBMS_VERSION "06.40.0002 PostgreSQL 6.4" +#define POSTGRESDRIVERVERSION "06.40.0002" #ifdef WIN32 #define DRIVER_FILE_NAME "PSQLODBC.DLL" @@ -116,12 +116,31 @@ typedef struct GlobalValues_ char bools_as_char; char lie; char parse; + char cancel_as_freestmt; char extra_systable_prefixes[MEDIUM_REGISTRY_LEN]; char conn_settings[LARGE_REGISTRY_LEN]; FILE* mylogFP; FILE* qlogFP; } GLOBAL_VALUES; +typedef struct StatementOptions_ { + int maxRows; + int maxLength; + int rowset_size; + int keyset_size; + int cursor_type; + int scroll_concurrency; + int retrieve_data; + int bind_size; /* size of each structure if using Row Binding */ +} StatementOptions; + +/* Used to pass extra query info to send_query */ +typedef struct QueryInfo_ { + int row_size; + QResultClass *result_in; + char *cursor; +} QueryInfo; + #define PG_TYPE_LO -999 /* hack until permanent type available */ #define PG_TYPE_LO_NAME "lo" diff --git a/src/interfaces/odbc/psqlodbc.rc b/src/interfaces/odbc/psqlodbc.rc index 779e82fea6..b6874da6fa 100644 --- a/src/interfaces/odbc/psqlodbc.rc +++ b/src/interfaces/odbc/psqlodbc.rc @@ -101,6 +101,8 @@ BEGIN WS_TABSTOP,140,35,80,10 CONTROL "&Use Declare/Fetch",DRV_USEDECLAREFETCH,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,15,50,80,10 + CONTROL "Cancel as FreeStmt (Exp)",DRV_CANCELASFREESTMT,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,140,50,105,10 GROUPBOX "Unknown Sizes",IDC_STATIC,10,65,175,25 CONTROL "Maximum",DRV_UNKNOWN_MAX,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,15,76,45,10 @@ -202,8 +204,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 6,40,0,1 - PRODUCTVERSION 6,40,0,1 + FILEVERSION 6,40,0,2 + PRODUCTVERSION 6,40,0,2 FILEFLAGSMASK 0x3L #ifdef _DEBUG FILEFLAGS 0x1L @@ -221,12 +223,12 @@ BEGIN VALUE "Comments", "PostgreSQL ODBC driver for Windows 95\0" VALUE "CompanyName", "Insight Distribution Systems\0" VALUE "FileDescription", "PostgreSQL Driver\0" - VALUE "FileVersion", " 6.40.0001\0" + VALUE "FileVersion", " 6.40.0002\0" VALUE "InternalName", "psqlodbc\0" VALUE "LegalTrademarks", "ODBC(TM) is a trademark of Microsoft Corporation. Microsoft® is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation.\0" VALUE "OriginalFilename", "psqlodbc.dll\0" VALUE "ProductName", "Microsoft Open Database Connectivity\0" - VALUE "ProductVersion", " 6.40.0001\0" + VALUE "ProductVersion", " 6.40.0002\0" END END BLOCK "VarFileInfo" diff --git a/src/interfaces/odbc/qresult.c b/src/interfaces/odbc/qresult.c index d5ee8407c8..8e5ffdf55c 100644 --- a/src/interfaces/odbc/qresult.c +++ b/src/interfaces/odbc/qresult.c @@ -48,6 +48,30 @@ QR_set_num_fields(QResultClass *self, int new_num_fields) mylog("exit QR_set_num_fields\n"); } +void +QR_set_position(QResultClass *self, int pos) +{ + self->tupleField = self->backend_tuples + ((self->base + pos) * self->num_fields); +} + +void +QR_set_cache_size(QResultClass *self, int cache_size) +{ + self->cache_size = cache_size; +} + +void +QR_set_rowset_size(QResultClass *self, int rowset_size) +{ + self->rowset_size = rowset_size; +} + +void +QR_inc_base(QResultClass *self, int base_inc) +{ + self->base += base_inc; +} + /************************************/ /* CLASS QResult */ /************************************/ @@ -77,9 +101,15 @@ QResultClass *rv; rv->inTuples = FALSE; rv->fcount = 0; rv->fetch_count = 0; + rv->base = 0; + rv->currTuple = -1; rv->num_fields = 0; rv->tupleField = NULL; rv->cursor = NULL; + + rv->cache_size = globals.fetch_max; + rv->rowset_size = 1; + } mylog("exit QR_Constructor\n"); @@ -178,6 +208,8 @@ int num_fields = self->num_fields; char QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor) { +int tuple_size; + // If called from send_query the first time (conn != NULL), // then set the inTuples state, // and read the tuples. If conn is NULL, @@ -186,7 +218,7 @@ QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor) if (conn != NULL) { self->conn = conn; - mylog("QR_fetch_tuples: cursor = '%s', self->cursor=%u\n", cursor, self->cursor); + mylog("QR_fetch_tuples: cursor = '%s', self->cursor=%u\n", (cursor==NULL)?"":cursor, self->cursor); if (self->cursor) free(self->cursor); @@ -199,7 +231,7 @@ QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor) } self->cursor = strdup(cursor); } - + // Read the field attributes. // $$$$ Should do some error control HERE! $$$$ if ( CI_read_fields(self->fields, self->conn)) { @@ -214,9 +246,14 @@ QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor) mylog("QR_fetch_tuples: past CI_read_fields: num_fields = %d\n", self->num_fields); + if (globals.use_declarefetch) + tuple_size = self->cache_size; + else + tuple_size = TUPLE_MALLOC_INC; + /* allocate memory for the tuple cache */ - mylog("MALLOC: fetch_max = %d, size = %d\n", globals.fetch_max, self->num_fields * sizeof(TupleField) * globals.fetch_max); - self->backend_tuples = (TupleField *) malloc(self->num_fields * sizeof(TupleField) * globals.fetch_max); + mylog("MALLOC: tuple_size = %d, size = %d\n", tuple_size, self->num_fields * sizeof(TupleField) * tuple_size); + self->backend_tuples = (TupleField *) malloc(self->num_fields * sizeof(TupleField) * tuple_size); if ( ! self->backend_tuples) { self->status = PGRES_FATAL_ERROR; QR_set_message(self, "Could not get memory for tuple cache."); @@ -225,13 +262,16 @@ QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor) self->inTuples = TRUE; + /* Force a read to occur in next_tuple */ - self->fcount = globals.fetch_max+1; - self->fetch_count = globals.fetch_max+1; + self->fcount = tuple_size+1; + self->fetch_count = tuple_size+1; + self->base = 0; return QR_next_tuple(self); } else { + // Always have to read the field attributes. // But we dont have to reallocate memory for them! @@ -257,9 +297,10 @@ QResultClass *res; sprintf(buf, "close %s", self->cursor); mylog("QResult: closing cursor: '%s'\n", buf); - res = CC_send_query(self->conn, buf, NULL, NULL); + res = CC_send_query(self->conn, buf, NULL); self->inTuples = FALSE; + self->currTuple = -1; free(self->cursor); self->cursor = NULL; @@ -274,7 +315,7 @@ QResultClass *res; if (CC_cursor_count(self->conn) == 0) { mylog("QResult: END transaction on conn=%u\n", self->conn); - res = CC_send_query(self->conn, "END", NULL, NULL); + res = CC_send_query(self->conn, "END", NULL); CC_set_no_trans(self->conn); @@ -300,9 +341,14 @@ SocketClass *sock; /* Speed up access */ int fetch_count = self->fetch_count; int fcount = self->fcount; +int fetch_size, offset= 0; +int end_tuple = self->rowset_size + self->base; +char corrected = FALSE; TupleField *the_tuples = self->backend_tuples; static char msgbuffer[MAX_MESSAGE_LEN+1]; char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont need static +char fetch[128]; +QueryInfo qi; if (fetch_count < fcount) { /* return a row from cache */ mylog("next_tuple: fetch_count < fcount: returning tuple %d, fcount = %d\n", fetch_count, fcount); @@ -310,9 +356,9 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont self->fetch_count++; return TRUE; } - else if (self->fcount < globals.fetch_max) { /* last row from cache */ - // We are done because we didn't even get FETCH_MAX tuples - mylog("next_tuple: fcount < FETCH_MAX: fcount = %d, fetch_count = %d\n", fcount, fetch_count); + else if (self->fcount < self->cache_size) { /* last row from cache */ + // We are done because we didn't even get CACHE_SIZE tuples + mylog("next_tuple: fcount < CACHE_SIZE: fcount = %d, fetch_count = %d\n", fcount, fetch_count); self->tupleField = NULL; self->status = PGRES_END_TUPLES; return -1; /* end of tuples */ @@ -324,8 +370,8 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont and read the tuples. */ self->tupleField = NULL; + if ( ! self->inTuples) { - char fetch[128]; if ( ! globals.use_declarefetch) { mylog("next_tuple: ALL_ROWS: done, fcount = %d, fetch_count = %d\n", fcount, fetch_count); @@ -334,20 +380,54 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont return -1; /* end of tuples */ } - sprintf(fetch, "fetch %d in %s", globals.fetch_max, self->cursor); + if (self->base == fcount) { /* not a correction */ - mylog("next_tuple: sending actual fetch (%d) query '%s'\n", globals.fetch_max, fetch); + /* Determine the optimum cache size. */ + if (globals.fetch_max % self->rowset_size == 0) + fetch_size = globals.fetch_max; + else if (self->rowset_size < globals.fetch_max) + fetch_size = (globals.fetch_max / self->rowset_size) * self->rowset_size; + else + fetch_size = self->rowset_size; + + self->cache_size = fetch_size; + self->fetch_count = 1; + } + else { /* need to correct */ + + corrected = TRUE; + + fetch_size = end_tuple - fcount; + + self->cache_size += fetch_size; + + offset = self->fetch_count; + self->fetch_count++; + + } + + + self->backend_tuples = (TupleField *) realloc(self->backend_tuples, self->num_fields * sizeof(TupleField) * self->cache_size); + if ( ! self->backend_tuples) { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Out of memory while reading tuples."); + return FALSE; + } + sprintf(fetch, "fetch %d in %s", fetch_size, self->cursor); + + mylog("next_tuple: sending actual fetch (%d) query '%s'\n", fetch_size, fetch); // don't read ahead for the next tuple (self) ! - res = CC_send_query(self->conn, fetch, self, NULL); + qi.row_size = self->cache_size; + qi.result_in = self; + qi.cursor = NULL; + res = CC_send_query(self->conn, fetch, &qi); if (res == NULL) { self->status = PGRES_FATAL_ERROR; QR_set_message(self, "Error fetching next group."); return FALSE; } self->inTuples = TRUE; - /* This is a true fetch, like SQLFetch() */ - self->fetch_count = 1; } else { mylog("next_tuple: inTuples = true, falling through: fcount = %d, fetch_count = %d\n", self->fcount, self->fetch_count); @@ -357,11 +437,14 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont */ self->fetch_count = 0; } - // fall through and read the next group + } + + if ( ! corrected) { + self->base = 0; + self->fcount = 0; } - self->fcount = 0; sock = CC_get_socket(self->conn); self->tupleField = NULL; @@ -377,11 +460,11 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont case 'B': /* Tuples in binary format */ case 'D': /* Tuples in ASCII format */ - if ( ! globals.use_declarefetch && self->fcount > 0 && ! (self->fcount % globals.fetch_max)) { + if ( ! globals.use_declarefetch && self->fcount > 0 && ! (self->fcount % TUPLE_MALLOC_INC)) { size_t old_size = self->fcount * self->num_fields * sizeof(TupleField); mylog("REALLOC: old_size = %d\n", old_size); - self->backend_tuples = (TupleField *) realloc(self->backend_tuples, old_size + (self->num_fields * sizeof(TupleField) * globals.fetch_max)); + self->backend_tuples = (TupleField *) realloc(self->backend_tuples, old_size + (self->num_fields * sizeof(TupleField) * TUPLE_MALLOC_INC)); if ( ! self->backend_tuples) { self->status = PGRES_FATAL_ERROR; QR_set_message(self, "Out of memory while reading tuples."); @@ -412,7 +495,7 @@ char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont mylog("_next_tuple: 'C' fetch_max && fcount = %d\n", self->fcount); /* set to first row */ - self->tupleField = self->backend_tuples; // the_tuples; + self->tupleField = self->backend_tuples + (offset * self->num_fields); return TRUE; } else { // We are surely done here (we read 0 tuples) @@ -532,5 +615,6 @@ ColumnInfoClass *flds; } else bmp <<= 1; } + self->currTuple++; return TRUE; } diff --git a/src/interfaces/odbc/qresult.h b/src/interfaces/odbc/qresult.h index 7cc2303173..f26381fc21 100644 --- a/src/interfaces/odbc/qresult.h +++ b/src/interfaces/odbc/qresult.h @@ -44,8 +44,13 @@ struct QResultClass_ { // Stuff for declare/fetch tuples int fetch_count; // logical rows read so far int fcount; // actual rows read in the fetch + int currTuple; + int base; int num_fields; // number of fields in the result + int cache_size; + int rowset_size; + QueryResultCode status; char *message; @@ -93,7 +98,7 @@ struct QResultClass_ { #define QR_get_status(self) (self->status) // Core Functions -QResultClass *QR_Constructor(void); +QResultClass *QR_Constructor(); void QR_Destructor(QResultClass *self); char QR_read_tuple(QResultClass *self, char binary); int QR_next_tuple(QResultClass *self); @@ -104,5 +109,10 @@ void QR_set_command(QResultClass *self, char *msg); void QR_set_notice(QResultClass *self, char *msg); void QR_set_num_fields(QResultClass *self, int new_num_fields); /* manual result only */ + +void QR_inc_base(QResultClass *self, int base_inc); +void QR_set_cache_size(QResultClass *self, int cache_size); +void QR_set_rowset_size(QResultClass *self, int rowset_size); +void QR_set_position(QResultClass *self, int pos); #endif diff --git a/src/interfaces/odbc/resource.h b/src/interfaces/odbc/resource.h index 16db18bbd2..81e3c9961f 100644 --- a/src/interfaces/odbc/resource.h +++ b/src/interfaces/odbc/resource.h @@ -44,6 +44,7 @@ #define DRV_EXTRASYSTABLEPREFIXES 1051 #define DS_ROWVERSIONING 1052 #define DRV_PARSE 1052 +#define DRV_CANCELASFREESTMT 1053 #define IDC_OPTIONS 1054 #define DRV_KSQO 1055 #define DS_PG64 1057 diff --git a/src/interfaces/odbc/results.c b/src/interfaces/odbc/results.c index 5d9dfc30fb..7f18f6a205 100644 --- a/src/interfaces/odbc/results.c +++ b/src/interfaces/odbc/results.c @@ -8,7 +8,7 @@ * * API functions: SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes, * SQLGetData, SQLFetch, SQLExtendedFetch, - * SQLMoreResults(NI), SQLSetPos(NI), SQLSetScrollOptions(NI), + * SQLMoreResults(NI), SQLSetPos, SQLSetScrollOptions(NI), * SQLSetCursorName, SQLGetCursorName * * Comments: See "notice.txt" for copyright and license information. @@ -57,13 +57,18 @@ char *msg, *ptr; 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 ? 0 : QR_get_num_tuples(res); + *pcrow = globals.use_declarefetch ? -1 : QR_get_num_tuples(res); return SQL_SUCCESS; } } @@ -176,6 +181,7 @@ Int4 fieldtype = 0; int precision = 0; ConnInfo *ci; char parse_ok; +char buf[255]; mylog("%s: entering...\n", func); @@ -247,7 +253,8 @@ char parse_ok; if (icol >= QR_NumResultCols(result)) { stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; stmt->errormsg = "Invalid column number in DescribeCol."; - SC_log_error(func, "", stmt); + sprintf(buf, "Col#=%d, #Cols=%d", icol, QR_NumResultCols(result)); + SC_log_error(func, buf, stmt); return SQL_ERROR; } @@ -610,7 +617,6 @@ int num_cols, num_rows; Int4 field_type; void *value; int result; -char multiple; mylog("SQLGetData: enter, stmt=%u\n", stmt); @@ -674,7 +680,7 @@ mylog("SQLGetData: enter, stmt=%u\n", stmt); mylog(" value = '%s'\n", value); } else { /* its a SOCKET result (backend data) */ - if (stmt->currTuple == -1 || ! res || QR_end_tuples(res)) { + 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); @@ -690,14 +696,12 @@ mylog("SQLGetData: enter, stmt=%u\n", stmt); mylog("**** SQLGetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value); - /* Is this another call for the same column to retrieve more data? */ - multiple = (icol == stmt->current_col) ? TRUE : FALSE; + stmt->current_col = icol; result = copy_and_convert_field(stmt, field_type, value, - fCType, rgbValue, cbValueMax, pcbValue, multiple); + fCType, rgbValue, cbValueMax, pcbValue); - - stmt->current_col = icol; + stmt->current_col = -1; switch(result) { case COPY_OK: @@ -725,7 +729,7 @@ mylog("SQLGetData: enter, stmt=%u\n", stmt); return SQL_ERROR; case COPY_NO_DATA_FOUND: - SC_log_error(func, "no data found", stmt); + /* SC_log_error(func, "no data found", stmt); */ return SQL_NO_DATA_FOUND; default: @@ -736,70 +740,27 @@ mylog("SQLGetData: enter, stmt=%u\n", stmt); } } -// Returns data for bound columns in the current row ("hstmt->iCursor"), -// advances the cursor. - -RETCODE SQL_API SQLFetch( - HSTMT hstmt) +RETCODE +SC_fetch(StatementClass *stmt) { -static char *func = "SQLFetch"; -StatementClass *stmt = (StatementClass *) hstmt; -QResultClass *res; -int retval; +static char *func = "SC_fetch"; +QResultClass *res = stmt->result; +int retval, result; Int2 num_cols, lf; Oid type; char *value; ColumnInfoClass *ci; // TupleField *tupleField; -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; - } - + stmt->last_fetch_count = 0; ci = QR_get_fields(res); /* the column info */ - 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; - } - mylog("manual_result = %d, use_declarefetch = %d\n", stmt->manual_result, globals.use_declarefetch); if ( stmt->manual_result || ! globals.use_declarefetch) { if (stmt->currTuple >= QR_get_num_tuples(res) -1 || - (stmt->maxRows > 0 && stmt->currTuple == stmt->maxRows - 1)) { + (stmt->options.maxRows > 0 && stmt->currTuple == stmt->options.maxRows - 1)) { /* if at the end of the tuples, return "no data found" and set the cursor past the end of the result set @@ -833,10 +794,16 @@ mylog("SQLFetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result); num_cols = QR_NumResultCols(res); + result = SQL_SUCCESS; + stmt->last_fetch_count = 1; + for (lf=0; lf < num_cols; lf++) { mylog("fetch: cols=%d, lf=%d, stmt = %u, stmt->bindings = %u, buffer[] = %u\n", num_cols, lf, stmt, stmt->bindings, stmt->bindings[lf].buffer); + /* reset for SQLGetData */ + stmt->bindings[lf].data_left = -1; + if (stmt->bindings[lf].buffer != NULL) { // this column has a binding @@ -855,13 +822,12 @@ mylog("SQLFetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result); value = QR_get_value_backend_row(res, stmt->currTuple, lf); } - mylog("value = '%s'\n", value); + mylog("value = '%s'\n", (value==NULL)?"":value); retval = copy_and_convert_field_bindinfo(stmt, type, value, lf); mylog("copy_and_convert: retval = %d\n", retval); - switch(retval) { case COPY_OK: break; /* OK, do next bound column */ @@ -870,37 +836,99 @@ mylog("SQLFetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result); stmt->errormsg = "Received an unsupported type from Postgres."; stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; SC_log_error(func, "", stmt); - return SQL_ERROR; + result = SQL_ERROR; + break; 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; + result = SQL_ERROR; + break; case COPY_RESULT_TRUNCATED: stmt->errornumber = STMT_TRUNCATED; stmt->errormsg = "The buffer was too small for the result."; - return SQL_SUCCESS_WITH_INFO; + result = SQL_SUCCESS_WITH_INFO; + break; case COPY_GENERAL_ERROR: /* error msg already filled in */ SC_log_error(func, "", stmt); - return SQL_ERROR; + result = SQL_ERROR; + break; + /* This would not be meaningful in SQLFetch. */ case COPY_NO_DATA_FOUND: - SC_log_error(func, "no data found", stmt); - return SQL_NO_DATA_FOUND; + break; default: stmt->errormsg = "Unrecognized return value from copy_and_convert_field."; stmt->errornumber = STMT_INTERNAL_ERROR; SC_log_error(func, "", stmt); - return SQL_ERROR; + result = SQL_ERROR; + break; } } } - return SQL_SUCCESS; + return result; +} + + +// 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; + } + + 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). @@ -914,9 +942,10 @@ RETCODE SQL_API SQLExtendedFetch( { static char *func = "SQLExtendedFetch"; StatementClass *stmt = (StatementClass *) hstmt; -int num_tuples; +QResultClass *res; +int num_tuples, i, save_rowset_size; RETCODE result; - +char truncated, error; mylog("SQLExtendedFetch: stmt=%u\n", stmt); @@ -925,43 +954,108 @@ mylog("SQLExtendedFetch: stmt=%u\n", stmt); return SQL_INVALID_HANDLE; } - if ( globals.use_declarefetch) { - SC_log_error(func, "SQLExtendedFetch with UseDeclareFetch not yet supported", stmt); + 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 (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) - *rgfRowStatus = SQL_ROW_NOROW; + for (i = 0; i < stmt->options.rowset_size; i++) + *(rgfRowStatus + i) = SQL_ROW_NOROW; + if (pcrow) *pcrow = 0; - num_tuples = QR_get_num_tuples(stmt->result); + 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); - /* If already before result set, return no data found */ - if (stmt->currTuple <= 0) - return SQL_NO_DATA_FOUND; - stmt->currTuple -= 2; + /* 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->currTuple = -1; + stmt->rowset_start = 0; break; case SQL_FETCH_LAST: mylog("SQL_FETCH_LAST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); - stmt->currTuple = num_tuples <= 0 ? -1 : (num_tuples - 2); + + stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size) ; break; case SQL_FETCH_ABSOLUTE: @@ -969,17 +1063,31 @@ mylog("SQLExtendedFetch: stmt=%u\n", stmt); /* 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->currTuple = irow-2; + stmt->rowset_start = irow - 1; } /* Position with respect to the end of the result set */ else { - stmt->currTuple = num_tuples + irow - 1; + 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; default: @@ -988,18 +1096,95 @@ mylog("SQLExtendedFetch: stmt=%u\n", stmt); } - mylog("SQLExtendedFetch: new currTuple = %d\n", stmt->currTuple); - result = SQLFetch(hstmt); - - if (result == SQL_SUCCESS) { - if (rgfRowStatus) - *rgfRowStatus = SQL_ROW_SUCCESS; - if (pcrow) - *pcrow = 1; + /***********************************/ + /* 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; + } } - return result; + /* 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; } @@ -1014,8 +1199,8 @@ RETCODE SQL_API SQLMoreResults( return SQL_NO_DATA_FOUND; } -// This positions the cursor within a block of data. - +// 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, @@ -1023,12 +1208,57 @@ RETCODE SQL_API SQLSetPos( UWORD fLock) { static char *func = "SQLSetPos"; -char buf[128]; +StatementClass *stmt = (StatementClass *) hstmt; +QResultClass *res; +int num_cols, i; +BindInfoClass *bindings = stmt->bindings; - sprintf(buf, "SQLSetPos not implemented: irow=%d, fOption=%d, fLock=%d\n", irow, fOption, fLock); + 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; - SC_log_error(func, buf, (StatementClass *) hstmt); - return SQL_ERROR; } // Sets options that control the behavior of cursors. diff --git a/src/interfaces/odbc/socket.h b/src/interfaces/odbc/socket.h index d57d012a48..0b4b9101d6 100644 --- a/src/interfaces/odbc/socket.h +++ b/src/interfaces/odbc/socket.h @@ -21,8 +21,18 @@ #include #include #include + #define closesocket(xxx) close(xxx) #define SOCKETFD int + +#ifndef INADDR_NONE +#ifndef _IN_ADDR_T +#define _IN_ADDR_T +typedef unsigned int in_addr_t; +#endif +#define INADDR_NONE ((in_addr_t)-1) +#endif + #else #include #define SOCKETFD SOCKET diff --git a/src/interfaces/odbc/statement.c b/src/interfaces/odbc/statement.c index db81491591..f185e2220a 100644 --- a/src/interfaces/odbc/statement.c +++ b/src/interfaces/odbc/statement.c @@ -97,6 +97,14 @@ StatementClass *stmt; *phstmt = (HSTMT) stmt; + /* Copy default statement options based from Connection options + */ + stmt->options = conn->stmtOptions; + + + /* Save the handle for later */ + stmt->phstmt = phstmt; + return SQL_SUCCESS; } @@ -166,6 +174,18 @@ StatementClass *stmt = (StatementClass *) hstmt; /********************************************************************** * StatementClass implementation */ +void +InitializeStatementOptions(StatementOptions *opt) +{ + opt->maxRows = 0; // driver returns all rows + opt->maxLength = 0; // driver returns all data for char/binary + opt->rowset_size = 1; + opt->keyset_size = 0; // fully keyset driven is the default + opt->scroll_concurrency = SQL_CONCUR_READ_ONLY; + opt->cursor_type = SQL_CURSOR_FORWARD_ONLY; + opt->bind_size = 0; /* default is to bind by column */ + opt->retrieve_data = SQL_RD_ON; +} StatementClass * SC_Constructor(void) @@ -175,40 +195,51 @@ StatementClass *rv; rv = (StatementClass *) malloc(sizeof(StatementClass)); if (rv) { rv->hdbc = NULL; /* no connection associated yet */ + rv->phstmt = NULL; rv->result = NULL; rv->manual_result = FALSE; rv->prepare = FALSE; rv->status = STMT_ALLOCATED; - rv->maxRows = 0; // driver returns all rows - rv->rowset_size = 1; - rv->keyset_size = 0; // fully keyset driven is the default - rv->scroll_concurrency = SQL_CONCUR_READ_ONLY; - rv->cursor_type = SQL_CURSOR_FORWARD_ONLY; + rv->internal = FALSE; + rv->errormsg = NULL; rv->errornumber = 0; rv->errormsg_created = FALSE; + rv->statement = NULL; rv->stmt_with_params[0] = '\0'; rv->statement_type = STMT_TYPE_UNKNOWN; + rv->bindings = NULL; rv->bindings_allocated = 0; + rv->parameters_allocated = 0; rv->parameters = 0; + rv->currTuple = -1; + rv->rowset_start = -1; rv->current_col = -1; - rv->result = 0; + rv->bind_row = 0; + rv->last_fetch_count = 0; + rv->save_rowset_size = -1; + rv->data_at_exec = -1; rv->current_exec_param = -1; rv->put_data = FALSE; + rv->lobj_fd = -1; - rv->internal = FALSE; rv->cursor_name[0] = '\0'; + /* Parse Stuff */ rv->ti = NULL; rv->fi = NULL; rv->ntab = 0; rv->nfld = 0; rv->parse_status = STMT_PARSE_NONE; + + + /* Clear Statement Options -- defaults will be set in AllocStmt */ + memset(&rv->options, 0, sizeof(StatementOptions)); } return rv; } @@ -361,7 +392,7 @@ mylog("recycle statement: self= %u\n", self); conn = SC_get_conn(self); if ( ! CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) { - CC_send_query(conn, "ABORT", NULL, NULL); + CC_send_query(conn, "ABORT", NULL); CC_set_no_trans(conn); } break; @@ -405,11 +436,18 @@ mylog("recycle statement: self= %u\n", self); self->result = NULL; } + /****************************************************************/ + /* Reset only parameters that have anything to do with results */ + /****************************************************************/ + self->status = STMT_READY; self->manual_result = FALSE; // very important self->currTuple = -1; + self->rowset_start = -1; self->current_col = -1; + self->bind_row = 0; + self->last_fetch_count = 0; self->errormsg = NULL; self->errornumber = 0; @@ -451,6 +489,7 @@ SC_unbind_cols(StatementClass *self) Int2 lf; for(lf = 0; lf < self->bindings_allocated; lf++) { + self->bindings[lf].data_left = -1; self->bindings[lf].buflen = 0; self->bindings[lf].buffer = NULL; self->bindings[lf].used = NULL; @@ -534,6 +573,7 @@ ConnectionClass *conn; QResultClass *res; char ok, was_ok, was_nonfatal; Int2 oldstatus, numcols; +QueryInfo qi; conn = SC_get_conn(self); @@ -545,7 +585,7 @@ Int2 oldstatus, numcols; if ( ! self->internal && ! CC_is_in_trans(conn) && (globals.use_declarefetch || STMT_UPDATE(self))) { mylog(" about to begin a transaction on statement = %u\n", self); - res = CC_send_query(conn, "BEGIN", NULL, NULL); + res = CC_send_query(conn, "BEGIN", NULL); if ( ! res) { self->errormsg = "Could not begin a transaction"; self->errornumber = STMT_EXEC_ERROR; @@ -587,14 +627,26 @@ Int2 oldstatus, numcols; /* send the declare/select */ - self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL); + self->result = CC_send_query(conn, self->stmt_with_params, NULL); if (globals.use_declarefetch && self->result != NULL) { + + QR_Destructor(self->result); + /* That worked, so now send the fetch to start getting data back */ - sprintf(fetch, "fetch %d in %s", globals.fetch_max, self->cursor_name); + qi.result_in = NULL; + qi.cursor = self->cursor_name; + qi.row_size = globals.fetch_max; + + /* Most likely the rowset size will not be set by the application until + after the statement is executed, so might as well use the cache size. + The qr_next_tuple() function will correct for any discrepancies in + sizes and adjust the cache accordingly. + */ + + sprintf(fetch, "fetch %d in %s", qi.row_size, self->cursor_name); - // Save the cursor in the result for later use - self->result = CC_send_query( conn, fetch, NULL, self->cursor_name); + self->result = CC_send_query( conn, fetch, &qi); } mylog(" done sending the query:\n"); @@ -604,11 +656,11 @@ Int2 oldstatus, numcols; } else { // not a SELECT statement so don't use a cursor mylog(" its NOT a select statement: stmt=%u\n", self); - self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL); + self->result = CC_send_query(conn, self->stmt_with_params, NULL); // If we are in autocommit, we must send the commit. if ( ! self->internal && CC_is_in_autocommit(conn) && STMT_UPDATE(self)) { - CC_send_query(conn, "COMMIT", NULL, NULL); + CC_send_query(conn, "COMMIT", NULL); CC_set_no_trans(conn); } @@ -630,6 +682,7 @@ Int2 oldstatus, numcols; self->currTuple = -1; /* set cursor before the first tuple in the list */ self->current_col = -1; + self->rowset_start = -1; /* see if the query did return any result columns */ numcols = QR_NumResultCols(self->result); @@ -692,7 +745,7 @@ SC_log_error(char *func, char *desc, StatementClass *self) qlog(" stmt_with_params='%s'\n", self->stmt_with_params); qlog(" data_at_exec=%d, current_exec_param=%d, put_data=%d\n", self->data_at_exec, self->current_exec_param, self->put_data); qlog(" currTuple=%d, current_col=%d, lobj_fd=%d\n", self->currTuple, self->current_col, self->lobj_fd); - qlog(" maxRows=%d, rowset_size=%d, keyset_size=%d, cursor_type=%d, scroll_concurrency=%d\n", self->maxRows, self->rowset_size, self->keyset_size, self->cursor_type, self->scroll_concurrency); + qlog(" maxRows=%d, rowset_size=%d, keyset_size=%d, cursor_type=%d, scroll_concurrency=%d\n", self->options.maxRows, self->options.rowset_size, self->options.keyset_size, self->options.cursor_type, self->options.scroll_concurrency); qlog(" cursor_name='%s'\n", self->cursor_name); qlog(" ----------------QResult Info -------------------------------\n"); diff --git a/src/interfaces/odbc/statement.h b/src/interfaces/odbc/statement.h index e9661ad83a..32a93a2965 100644 --- a/src/interfaces/odbc/statement.h +++ b/src/interfaces/odbc/statement.h @@ -66,6 +66,10 @@ typedef enum { #define STMT_CREATE_TABLE_ERROR 17 #define STMT_NO_CURSOR_NAME 18 #define STMT_INVALID_CURSOR_NAME 19 +#define STMT_INVALID_ARGUMENT_NO 20 +#define STMT_ROW_OUT_OF_RANGE 21 +#define STMT_OPERATION_CANCELLED 22 +#define STMT_INVALID_CURSOR_POSITION 23 /* statement types */ enum { @@ -93,6 +97,13 @@ enum { STMT_PARSE_FATAL, }; +/* Result style */ +enum { + STMT_FETCH_NONE = 0, + STMT_FETCH_NORMAL, + STMT_FETCH_EXTENDED, +}; + typedef struct { COL_INFO *col_info; /* cached SQLColumns info for this table */ char name[MAX_TABLE_LEN+1]; @@ -117,21 +128,16 @@ typedef struct { } FIELD_INFO; - /******** Statement Handle ***********/ struct StatementClass_ { ConnectionClass *hdbc; /* pointer to ConnectionClass this statement belongs to */ - QResultClass *result; /* result of the current statement */ + HSTMT FAR *phstmt; + StatementOptions options; STMT_Status status; char *errormsg; int errornumber; - int maxRows; - int rowset_size; - int keyset_size; - int cursor_type; - int scroll_concurrency; /* information on bindings */ BindInfoClass *bindings; /* array to store the binding information */ @@ -141,7 +147,11 @@ struct StatementClass_ { int parameters_allocated; ParameterInfoClass *parameters; - Int4 currTuple; + Int4 currTuple; /* current absolute row number (GetData, SetPos, SQLFetch) */ + int save_rowset_size; /* saved rowset size in case of change/FETCH_NEXT */ + int rowset_start; /* start of rowset (an absolute row number) */ + int bind_row; /* current offset for Multiple row/column binding */ + int last_fetch_count; /* number of rows retrieved in last fetch/extended fetch */ int current_col; /* current column for GetData -- used to handle multiple calls */ int lobj_fd; /* fd of the current large object */ @@ -181,6 +191,7 @@ struct StatementClass_ { /* Statement prototypes */ StatementClass *SC_Constructor(void); +void InitializeStatementOptions(StatementOptions *opt); char SC_Destructor(StatementClass *self); int statement_type(char *statement); char parse_statement(StatementClass *stmt);