diff --git a/src/interfaces/odbc/bind.c b/src/interfaces/odbc/bind.c index 945d23d64d..504dbc20eb 100644 --- a/src/interfaces/odbc/bind.c +++ b/src/interfaces/odbc/bind.c @@ -200,7 +200,12 @@ mylog("**** SQLBindCol: stmt = %u, icol = %d\n", stmt, icol); // - - - - - - - - - -// Returns the description of a parameter marker. +// Returns the description of a parameter marker. +// This function is listed as not being supported by SQLGetFunctions() because it is +// used to describe "parameter markers" (not bound parameters), in which case, +// the dbms should return info on the markers. Since Postgres doesn't support that, +// it is best to say this function is not supported and let the application assume a +// data type (most likely varchar). RETCODE SQL_API SQLDescribeParam( HSTMT hstmt, @@ -223,6 +228,8 @@ StatementClass *stmt = (StatementClass *) hstmt; ipar--; + // This implementation is not very good, since it is supposed to describe + // parameter markers, not bound parameters. if(pfSqlType) *pfSqlType = stmt->parameters[ipar].SQLType; @@ -252,25 +259,50 @@ RETCODE SQL_API SQLParamOptions( // - - - - - - - - - -// Returns the number of parameters in an SQL statement - +// This function should really talk to the dbms to determine the number of +// "parameter markers" (not bound parameters) in the statement. But, since +// Postgres doesn't support that, the driver should just count the number of markers +// and return that. The reason the driver just can't say this function is unsupported +// like it does for SQLDescribeParam is that some applications don't care and try +// to call it anyway. +// If the statement does not have parameters, it should just return 0. RETCODE SQL_API SQLNumParams( HSTMT hstmt, SWORD FAR *pcpar) { StatementClass *stmt = (StatementClass *) hstmt; +char in_quote = FALSE; +unsigned int i; if(!stmt) return SQL_INVALID_HANDLE; - // If the statement does not have parameters, it should just return 0. + if (pcpar) + *pcpar = 0; + else + return SQL_ERROR; - if (pcpar) { - *pcpar = stmt->parameters_allocated; + + if(!stmt->statement) { + // no statement has been allocated + stmt->errormsg = "SQLNumParams called with no statement ready."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return SQL_ERROR; + } else { + + for(i=0; i < strlen(stmt->statement); i++) { + + if(stmt->statement[i] == '?' && !in_quote) + (*pcpar)++; + else { + if (stmt->statement[i] == '\'') + in_quote = (in_quote ? FALSE : TRUE); + } + } + + return SQL_SUCCESS; } - - return SQL_SUCCESS; } /******************************************************************** diff --git a/src/interfaces/odbc/convert.c b/src/interfaces/odbc/convert.c index bad3f1a911..22da73e081 100644 --- a/src/interfaces/odbc/convert.c +++ b/src/interfaces/odbc/convert.c @@ -365,7 +365,12 @@ struct tm *tim; // truncate the data. // return COPY_RESULT_TRUNCATED; - *pcbValue = cbValueMax -1; + // LongVarBinary types do handle truncated multiple get calls + // through convert_lo(). + + if (pcbValue) + *pcbValue = cbValueMax -1; + return COPY_OK; } else { diff --git a/src/interfaces/odbc/dlg_specific.c b/src/interfaces/odbc/dlg_specific.c index 558fa66926..e04cc8b569 100644 --- a/src/interfaces/odbc/dlg_specific.c +++ b/src/interfaces/odbc/dlg_specific.c @@ -623,6 +623,15 @@ char temp[128]; globals.unknown_sizes = atoi(temp); + // Lie about supported functions? + SQLGetPrivateProfileString(DBMS_NAME, INI_LIE, "", + temp, sizeof(temp), ODBCINST_INI); + if ( temp[0] == '\0') + globals.lie = DEFAULT_LIE; + else + globals.lie = atoi(temp); + + // Readonly is stored in the driver section AND per datasource SQLGetPrivateProfileString(DBMS_NAME, INI_READONLY, "", temp, sizeof(temp), ODBCINST_INI); diff --git a/src/interfaces/odbc/dlg_specific.h b/src/interfaces/odbc/dlg_specific.h index bedf222937..3c4ac4a600 100644 --- a/src/interfaces/odbc/dlg_specific.h +++ b/src/interfaces/odbc/dlg_specific.h @@ -56,6 +56,7 @@ #define INI_FAKEOIDINDEX "FakeOidIndex" #define INI_SHOWOIDCOLUMN "ShowOidColumn" #define INI_SHOWSYSTEMTABLES "ShowSystemTables" +#define INI_LIE "Lie" #define INI_EXTRASYSTABLEPREFIXES "ExtraSysTablePrefixes" /* Connection Defaults */ @@ -74,6 +75,7 @@ #define DEFAULT_FAKEOIDINDEX 0 #define DEFAULT_SHOWOIDCOLUMN 0 #define DEFAULT_SHOWSYSTEMTABLES 0 // dont show system tables +#define DEFAULT_LIE 0 #define DEFAULT_EXTRASYSTABLEPREFIXES "dd_;" diff --git a/src/interfaces/odbc/info.c b/src/interfaces/odbc/info.c index ae6c6074f7..c619a230a4 100644 --- a/src/interfaces/odbc/info.c +++ b/src/interfaces/odbc/info.c @@ -307,7 +307,7 @@ char *p; case SQL_LOCK_TYPES: /* ODBC 2.0 */ // which lock types does SQLSetPos support? (bitmask) // SQLSetPos doesn't exist yet - *((DWORD *)rgbInfoValue) = 0; + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_LCK_NO_CHANGE | SQL_LCK_EXCLUSIVE | SQL_LCK_UNLOCK) : 0; if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -424,9 +424,9 @@ char *p; break; case SQL_MULT_RESULT_SETS: /* ODBC 1.0 */ - // do we support multiple result sets? + // do we support multiple result sets? Not really, but say yes anyway? if (pcbInfoValue) *pcbInfoValue = 1; - strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); break; case SQL_MULTIPLE_ACTIVE_TXN: /* ODBC 1.0 */ @@ -442,9 +442,7 @@ char *p; break; case SQL_NON_NULLABLE_COLUMNS: /* ODBC 1.0 */ - // I think you can have NOT NULL columns with one of dal Zotto's - // patches, but for now we'll say no. - *((WORD *)rgbInfoValue) = (WORD)SQL_NNC_NULL; + *((WORD *)rgbInfoValue) = (WORD)SQL_NNC_NON_NULL; if(pcbInfoValue) { *pcbInfoValue = 2; } break; @@ -516,13 +514,15 @@ char *p; case SQL_POS_OPERATIONS: /* ODBC 2.0 */ // what functions does SQLSetPos support? (bitmask) // SQLSetPos does not exist yet - *((DWORD *)rgbInfoValue) = 0; + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_POS_POSITION | SQL_POS_REFRESH | SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD) : 0; if(pcbInfoValue) { *pcbInfoValue = 4; } break; case SQL_POSITIONED_STATEMENTS: /* ODBC 2.0 */ // what 'positioned' functions are supported? (bitmask) - *((DWORD *)rgbInfoValue) = 0; + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_PS_POSITIONED_DELETE | + SQL_PS_POSITIONED_UPDATE | + SQL_PS_SELECT_FOR_UPDATE) : 0; if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -577,12 +577,15 @@ char *p; // Driver doesn't support keyset-driven or mixed cursors, so // not much point in saying row updates are supported if (pcbInfoValue) *pcbInfoValue = 1; - strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + strncpy_null((char *)rgbInfoValue, globals.lie ? "Y" : "N", (size_t)cbInfoValueMax); break; case SQL_SCROLL_CONCURRENCY: /* ODBC 1.0 */ // what concurrency options are supported BY THE CURSOR? (bitmask) - *((DWORD *)rgbInfoValue) = (SQL_SCCO_READ_ONLY); + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_SCCO_READ_ONLY | + SQL_SCCO_LOCK | + SQL_SCCO_OPT_ROWVER | + SQL_SCCO_OPT_VALUES) : (SQL_SCCO_READ_ONLY); if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -590,7 +593,11 @@ char *p; // what options are supported for scrollable cursors? (bitmask) // for declare/fetch, only FORWARD scrolling is allowed // otherwise, the result set is STATIC (to SQLExtendedFetch for example) - *((DWORD *)rgbInfoValue) = globals.use_declarefetch ? SQL_SO_FORWARD_ONLY : (SQL_SO_FORWARD_ONLY | SQL_SO_STATIC); + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_SO_FORWARD_ONLY | + SQL_SO_STATIC | + SQL_SO_KEYSET_DRIVEN | + SQL_SO_DYNAMIC | + SQL_SO_MIXED) : (globals.use_declarefetch ? SQL_SO_FORWARD_ONLY : (SQL_SO_FORWARD_ONLY | SQL_SO_STATIC)); if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -620,7 +627,7 @@ char *p; // can changes made inside a cursor be detected? (or something like that) // (bitmask) // only applies to SQLSetPos, which doesn't exist yet. - *((DWORD *)rgbInfoValue) = 0; + *((DWORD *)rgbInfoValue) = globals.lie ? (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES) : 0; if(pcbInfoValue) { *pcbInfoValue = 4; } break; @@ -805,149 +812,152 @@ RETCODE SQL_API SQLGetFunctions( { if (fFunction == SQL_API_ALL_FUNCTIONS) { + if (globals.lie) { + int i; + memset(pfExists, 0, sizeof(UWORD)*100); -#ifdef GETINFO_LIE - int i; - memset(pfExists, 0, sizeof(UWORD)*100); + pfExists[SQL_API_SQLALLOCENV] = TRUE; + pfExists[SQL_API_SQLFREEENV] = TRUE; + for (i = SQL_API_SQLALLOCCONNECT; i <= SQL_NUM_FUNCTIONS; i++) + pfExists[i] = TRUE; + for (i = SQL_EXT_API_START; i <= SQL_EXT_API_LAST; i++) + pfExists[i] = TRUE; + } + else { + memset(pfExists, 0, sizeof(UWORD)*100); - pfExists[SQL_API_SQLALLOCENV] = TRUE; - pfExists[SQL_API_SQLFREEENV] = TRUE; - for (i = SQL_API_SQLALLOCCONNECT; i <= SQL_NUM_FUNCTIONS; i++) - pfExists[i] = TRUE; - for (i = SQL_EXT_API_START; i <= SQL_EXT_API_LAST; i++) - pfExists[i] = TRUE; -#else - memset(pfExists, 0, sizeof(UWORD)*100); + // ODBC core functions + pfExists[SQL_API_SQLALLOCCONNECT] = TRUE; + pfExists[SQL_API_SQLALLOCENV] = TRUE; + pfExists[SQL_API_SQLALLOCSTMT] = TRUE; + pfExists[SQL_API_SQLBINDCOL] = TRUE; + pfExists[SQL_API_SQLCANCEL] = TRUE; + pfExists[SQL_API_SQLCOLATTRIBUTES] = TRUE; + pfExists[SQL_API_SQLCONNECT] = TRUE; + pfExists[SQL_API_SQLDESCRIBECOL] = TRUE; // partial + pfExists[SQL_API_SQLDISCONNECT] = TRUE; + pfExists[SQL_API_SQLERROR] = TRUE; + pfExists[SQL_API_SQLEXECDIRECT] = TRUE; + pfExists[SQL_API_SQLEXECUTE] = TRUE; + pfExists[SQL_API_SQLFETCH] = TRUE; + pfExists[SQL_API_SQLFREECONNECT] = TRUE; + pfExists[SQL_API_SQLFREEENV] = TRUE; + pfExists[SQL_API_SQLFREESTMT] = TRUE; + pfExists[SQL_API_SQLGETCURSORNAME] = TRUE; + pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE; + pfExists[SQL_API_SQLPREPARE] = TRUE; // complete? + pfExists[SQL_API_SQLROWCOUNT] = TRUE; + pfExists[SQL_API_SQLSETCURSORNAME] = TRUE; + pfExists[SQL_API_SQLSETPARAM] = FALSE; // odbc 1.0 + pfExists[SQL_API_SQLTRANSACT] = TRUE; - // ODBC core functions - pfExists[SQL_API_SQLALLOCCONNECT] = TRUE; - pfExists[SQL_API_SQLALLOCENV] = TRUE; - pfExists[SQL_API_SQLALLOCSTMT] = TRUE; - pfExists[SQL_API_SQLBINDCOL] = TRUE; - pfExists[SQL_API_SQLCANCEL] = TRUE; - pfExists[SQL_API_SQLCOLATTRIBUTES] = TRUE; - pfExists[SQL_API_SQLCONNECT] = TRUE; - pfExists[SQL_API_SQLDESCRIBECOL] = TRUE; // partial - pfExists[SQL_API_SQLDISCONNECT] = TRUE; - pfExists[SQL_API_SQLERROR] = TRUE; - pfExists[SQL_API_SQLEXECDIRECT] = TRUE; - pfExists[SQL_API_SQLEXECUTE] = TRUE; - pfExists[SQL_API_SQLFETCH] = TRUE; - pfExists[SQL_API_SQLFREECONNECT] = TRUE; - pfExists[SQL_API_SQLFREEENV] = TRUE; - pfExists[SQL_API_SQLFREESTMT] = TRUE; - pfExists[SQL_API_SQLGETCURSORNAME] = TRUE; - pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE; - pfExists[SQL_API_SQLPREPARE] = TRUE; // complete? - pfExists[SQL_API_SQLROWCOUNT] = TRUE; - pfExists[SQL_API_SQLSETCURSORNAME] = TRUE; - pfExists[SQL_API_SQLSETPARAM] = FALSE; // odbc 1.0 - pfExists[SQL_API_SQLTRANSACT] = TRUE; + // ODBC level 1 functions + pfExists[SQL_API_SQLBINDPARAMETER] = TRUE; + pfExists[SQL_API_SQLCOLUMNS] = TRUE; + pfExists[SQL_API_SQLDRIVERCONNECT] = TRUE; + pfExists[SQL_API_SQLGETCONNECTOPTION] = TRUE; // partial + pfExists[SQL_API_SQLGETDATA] = TRUE; + pfExists[SQL_API_SQLGETFUNCTIONS] = TRUE; + pfExists[SQL_API_SQLGETINFO] = TRUE; + pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; // partial + pfExists[SQL_API_SQLGETTYPEINFO] = TRUE; + pfExists[SQL_API_SQLPARAMDATA] = TRUE; + pfExists[SQL_API_SQLPUTDATA] = TRUE; + pfExists[SQL_API_SQLSETCONNECTOPTION] = TRUE; // partial + pfExists[SQL_API_SQLSETSTMTOPTION] = TRUE; + pfExists[SQL_API_SQLSPECIALCOLUMNS] = TRUE; + pfExists[SQL_API_SQLSTATISTICS] = TRUE; + pfExists[SQL_API_SQLTABLES] = TRUE; - // ODBC level 1 functions - pfExists[SQL_API_SQLBINDPARAMETER] = TRUE; - pfExists[SQL_API_SQLCOLUMNS] = TRUE; - pfExists[SQL_API_SQLDRIVERCONNECT] = TRUE; - pfExists[SQL_API_SQLGETCONNECTOPTION] = TRUE; // partial - pfExists[SQL_API_SQLGETDATA] = TRUE; - pfExists[SQL_API_SQLGETFUNCTIONS] = TRUE; - pfExists[SQL_API_SQLGETINFO] = TRUE; - pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; // partial - pfExists[SQL_API_SQLGETTYPEINFO] = TRUE; - pfExists[SQL_API_SQLPARAMDATA] = TRUE; - pfExists[SQL_API_SQLPUTDATA] = TRUE; - pfExists[SQL_API_SQLSETCONNECTOPTION] = TRUE; // partial - pfExists[SQL_API_SQLSETSTMTOPTION] = TRUE; - pfExists[SQL_API_SQLSPECIALCOLUMNS] = TRUE; - pfExists[SQL_API_SQLSTATISTICS] = TRUE; - pfExists[SQL_API_SQLTABLES] = TRUE; - - // ODBC level 2 functions - pfExists[SQL_API_SQLBROWSECONNECT] = FALSE; - pfExists[SQL_API_SQLCOLUMNPRIVILEGES] = FALSE; - pfExists[SQL_API_SQLDATASOURCES] = FALSE; // only implemented by DM - pfExists[SQL_API_SQLDESCRIBEPARAM] = TRUE; - pfExists[SQL_API_SQLDRIVERS] = FALSE; // only implemented by DM - pfExists[SQL_API_SQLEXTENDEDFETCH] = globals.use_declarefetch ? FALSE : TRUE; - pfExists[SQL_API_SQLFOREIGNKEYS] = TRUE; - pfExists[SQL_API_SQLMORERESULTS] = FALSE; - pfExists[SQL_API_SQLNATIVESQL] = TRUE; - pfExists[SQL_API_SQLNUMPARAMS] = TRUE; - pfExists[SQL_API_SQLPARAMOPTIONS] = FALSE; - 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_SQLTABLEPRIVILEGES] = FALSE; -#endif + // ODBC level 2 functions + pfExists[SQL_API_SQLBROWSECONNECT] = FALSE; + pfExists[SQL_API_SQLCOLUMNPRIVILEGES] = FALSE; + 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_SQLFOREIGNKEYS] = TRUE; + pfExists[SQL_API_SQLMORERESULTS] = TRUE; + pfExists[SQL_API_SQLNATIVESQL] = TRUE; + pfExists[SQL_API_SQLNUMPARAMS] = TRUE; + pfExists[SQL_API_SQLPARAMOPTIONS] = FALSE; + 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_SQLTABLEPRIVILEGES] = FALSE; + } } else { -#ifdef GETINFO_LIE - *pfExists = TRUE; -#else - switch(fFunction) { - case SQL_API_SQLALLOCCONNECT: *pfExists = TRUE; break; - case SQL_API_SQLALLOCENV: *pfExists = TRUE; break; - case SQL_API_SQLALLOCSTMT: *pfExists = TRUE; break; - case SQL_API_SQLBINDCOL: *pfExists = TRUE; break; - case SQL_API_SQLCANCEL: *pfExists = TRUE; break; - case SQL_API_SQLCOLATTRIBUTES: *pfExists = TRUE; break; - case SQL_API_SQLCONNECT: *pfExists = TRUE; break; - case SQL_API_SQLDESCRIBECOL: *pfExists = TRUE; break; // partial - case SQL_API_SQLDISCONNECT: *pfExists = TRUE; break; - case SQL_API_SQLERROR: *pfExists = TRUE; break; - case SQL_API_SQLEXECDIRECT: *pfExists = TRUE; break; - case SQL_API_SQLEXECUTE: *pfExists = TRUE; break; - case SQL_API_SQLFETCH: *pfExists = TRUE; break; - case SQL_API_SQLFREECONNECT: *pfExists = TRUE; break; - case SQL_API_SQLFREEENV: *pfExists = TRUE; break; - case SQL_API_SQLFREESTMT: *pfExists = TRUE; break; - case SQL_API_SQLGETCURSORNAME: *pfExists = TRUE; break; - case SQL_API_SQLNUMRESULTCOLS: *pfExists = TRUE; break; - case SQL_API_SQLPREPARE: *pfExists = TRUE; break; - case SQL_API_SQLROWCOUNT: *pfExists = TRUE; break; - case SQL_API_SQLSETCURSORNAME: *pfExists = TRUE; break; - case SQL_API_SQLSETPARAM: *pfExists = FALSE; break; // odbc 1.0 - case SQL_API_SQLTRANSACT: *pfExists = TRUE; break; - // ODBC level 1 functions - case SQL_API_SQLBINDPARAMETER: *pfExists = TRUE; break; - case SQL_API_SQLCOLUMNS: *pfExists = TRUE; break; - case SQL_API_SQLDRIVERCONNECT: *pfExists = TRUE; break; - case SQL_API_SQLGETCONNECTOPTION: *pfExists = TRUE; break; // partial - case SQL_API_SQLGETDATA: *pfExists = TRUE; break; - case SQL_API_SQLGETFUNCTIONS: *pfExists = TRUE; break; - case SQL_API_SQLGETINFO: *pfExists = TRUE; break; - case SQL_API_SQLGETSTMTOPTION: *pfExists = TRUE; break; // partial - case SQL_API_SQLGETTYPEINFO: *pfExists = TRUE; break; - case SQL_API_SQLPARAMDATA: *pfExists = TRUE; break; - case SQL_API_SQLPUTDATA: *pfExists = TRUE; break; - case SQL_API_SQLSETCONNECTOPTION: *pfExists = TRUE; break; // partial - case SQL_API_SQLSETSTMTOPTION: *pfExists = TRUE; break; - case SQL_API_SQLSPECIALCOLUMNS: *pfExists = TRUE; break; - case SQL_API_SQLSTATISTICS: *pfExists = TRUE; break; - case SQL_API_SQLTABLES: *pfExists = TRUE; break; + if (globals.lie) + *pfExists = TRUE; - // ODBC level 2 functions - case SQL_API_SQLBROWSECONNECT: *pfExists = FALSE; break; - case SQL_API_SQLCOLUMNPRIVILEGES: *pfExists = FALSE; break; - case SQL_API_SQLDATASOURCES: *pfExists = FALSE; break; // only implemented by DM - case SQL_API_SQLDESCRIBEPARAM: *pfExists = TRUE; break; - 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_SQLFOREIGNKEYS: *pfExists = TRUE; break; - case SQL_API_SQLMORERESULTS: *pfExists = FALSE; break; - case SQL_API_SQLNATIVESQL: *pfExists = TRUE; break; - case SQL_API_SQLNUMPARAMS: *pfExists = TRUE; break; - case SQL_API_SQLPARAMOPTIONS: *pfExists = FALSE; break; - 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_SQLTABLEPRIVILEGES: *pfExists = FALSE; break; - } -#endif + else { + + switch(fFunction) { + case SQL_API_SQLALLOCCONNECT: *pfExists = TRUE; break; + case SQL_API_SQLALLOCENV: *pfExists = TRUE; break; + case SQL_API_SQLALLOCSTMT: *pfExists = TRUE; break; + case SQL_API_SQLBINDCOL: *pfExists = TRUE; break; + case SQL_API_SQLCANCEL: *pfExists = TRUE; break; + case SQL_API_SQLCOLATTRIBUTES: *pfExists = TRUE; break; + case SQL_API_SQLCONNECT: *pfExists = TRUE; break; + case SQL_API_SQLDESCRIBECOL: *pfExists = TRUE; break; // partial + case SQL_API_SQLDISCONNECT: *pfExists = TRUE; break; + case SQL_API_SQLERROR: *pfExists = TRUE; break; + case SQL_API_SQLEXECDIRECT: *pfExists = TRUE; break; + case SQL_API_SQLEXECUTE: *pfExists = TRUE; break; + case SQL_API_SQLFETCH: *pfExists = TRUE; break; + case SQL_API_SQLFREECONNECT: *pfExists = TRUE; break; + case SQL_API_SQLFREEENV: *pfExists = TRUE; break; + case SQL_API_SQLFREESTMT: *pfExists = TRUE; break; + case SQL_API_SQLGETCURSORNAME: *pfExists = TRUE; break; + case SQL_API_SQLNUMRESULTCOLS: *pfExists = TRUE; break; + case SQL_API_SQLPREPARE: *pfExists = TRUE; break; + case SQL_API_SQLROWCOUNT: *pfExists = TRUE; break; + case SQL_API_SQLSETCURSORNAME: *pfExists = TRUE; break; + case SQL_API_SQLSETPARAM: *pfExists = FALSE; break; // odbc 1.0 + case SQL_API_SQLTRANSACT: *pfExists = TRUE; break; + + // ODBC level 1 functions + case SQL_API_SQLBINDPARAMETER: *pfExists = TRUE; break; + case SQL_API_SQLCOLUMNS: *pfExists = TRUE; break; + case SQL_API_SQLDRIVERCONNECT: *pfExists = TRUE; break; + case SQL_API_SQLGETCONNECTOPTION: *pfExists = TRUE; break; // partial + case SQL_API_SQLGETDATA: *pfExists = TRUE; break; + case SQL_API_SQLGETFUNCTIONS: *pfExists = TRUE; break; + case SQL_API_SQLGETINFO: *pfExists = TRUE; break; + case SQL_API_SQLGETSTMTOPTION: *pfExists = TRUE; break; // partial + case SQL_API_SQLGETTYPEINFO: *pfExists = TRUE; break; + case SQL_API_SQLPARAMDATA: *pfExists = TRUE; break; + case SQL_API_SQLPUTDATA: *pfExists = TRUE; break; + case SQL_API_SQLSETCONNECTOPTION: *pfExists = TRUE; break; // partial + case SQL_API_SQLSETSTMTOPTION: *pfExists = TRUE; break; + case SQL_API_SQLSPECIALCOLUMNS: *pfExists = TRUE; break; + case SQL_API_SQLSTATISTICS: *pfExists = TRUE; break; + case SQL_API_SQLTABLES: *pfExists = TRUE; break; + + // ODBC level 2 functions + case SQL_API_SQLBROWSECONNECT: *pfExists = FALSE; break; + case SQL_API_SQLCOLUMNPRIVILEGES: *pfExists = FALSE; break; + 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_SQLFOREIGNKEYS: *pfExists = TRUE; break; + case SQL_API_SQLMORERESULTS: *pfExists = TRUE; break; + case SQL_API_SQLNATIVESQL: *pfExists = TRUE; break; + case SQL_API_SQLNUMPARAMS: *pfExists = TRUE; break; + case SQL_API_SQLPARAMOPTIONS: *pfExists = FALSE; break; + 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_SQLTABLEPRIVILEGES: *pfExists = FALSE; break; + } + } } return SQL_SUCCESS; @@ -974,7 +984,7 @@ RETCODE result; char *tableType; char tables_query[MAX_STATEMENT_LEN]; char table_name[MAX_INFO_STRING], table_owner[MAX_INFO_STRING], relhasrules[MAX_INFO_STRING]; -SDWORD table_name_len, table_owner_len, relhasrules_len; +// SDWORD table_name_len, table_owner_len, relhasrules_len; ConnInfo *ci; char *prefix[32], prefixes[MEDIUM_REGISTRY_LEN]; char *table_type[32], table_types[MAX_INFO_STRING]; @@ -1082,7 +1092,7 @@ mylog("**** SQLTables(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(htbl_stmt, 1, SQL_C_CHAR, - table_name, MAX_INFO_STRING, &table_name_len); + table_name, MAX_INFO_STRING, NULL /* &table_name_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = tbl_stmt->errormsg; stmt->errornumber = tbl_stmt->errornumber; @@ -1091,7 +1101,7 @@ mylog("**** SQLTables(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(htbl_stmt, 2, SQL_C_CHAR, - table_owner, MAX_INFO_STRING, &table_owner_len); + table_owner, MAX_INFO_STRING, NULL /* &table_owner_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = tbl_stmt->errormsg; stmt->errornumber = tbl_stmt->errornumber; @@ -1099,7 +1109,7 @@ mylog("**** SQLTables(): ENTER, stmt=%u\n", stmt); return SQL_ERROR; } result = SQLBindCol(htbl_stmt, 3, SQL_C_CHAR, - relhasrules, MAX_INFO_STRING, &relhasrules_len); + relhasrules, MAX_INFO_STRING, NULL /* &relhasrules_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = tbl_stmt->errormsg; stmt->errornumber = tbl_stmt->errornumber; @@ -1229,9 +1239,11 @@ RETCODE result; char table_owner[MAX_INFO_STRING], table_name[MAX_INFO_STRING], field_name[MAX_INFO_STRING], field_type_name[MAX_INFO_STRING]; Int2 field_number, field_length, mod_length; Int4 field_type; -SDWORD table_owner_len, table_name_len, field_name_len, +char not_null[2]; +/* SDWORD table_owner_len, table_name_len, field_name_len, field_type_len, field_type_name_len, field_number_len, - field_length_len, mod_length_len; + field_length_len, mod_length_len, not_null_len; +*/ ConnInfo *ci; @@ -1249,7 +1261,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); // ********************************************************************** // Create the query to find out the columns (Note: pre 6.3 did not have the atttypmod field) // ********************************************************************** - sprintf(columns_query, "select u.usename, c.relname, a.attname, a.atttypid,t.typname, a.attnum, a.attlen, %s from pg_user u, pg_class c, pg_attribute a, pg_type t where " + sprintf(columns_query, "select u.usename, c.relname, a.attname, a.atttypid,t.typname, a.attnum, a.attlen, %s, a.attnotnull from pg_user u, pg_class c, pg_attribute a, pg_type t where " "int4out(u.usesysid) = int4out(c.relowner) and c.oid= a.attrelid and a.atttypid = t.oid and (a.attnum > 0)", PROTOCOL_62(ci) ? "a.attlen" : "a.atttypmod"); @@ -1280,7 +1292,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 1, SQL_C_CHAR, - table_owner, MAX_INFO_STRING, &table_owner_len); + table_owner, MAX_INFO_STRING, NULL /* &table_owner_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1289,7 +1301,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 2, SQL_C_CHAR, - table_name, MAX_INFO_STRING, &table_name_len); + table_name, MAX_INFO_STRING, NULL /* &table_name_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1298,7 +1310,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 3, SQL_C_CHAR, - field_name, MAX_INFO_STRING, &field_name_len); + field_name, MAX_INFO_STRING, NULL /* &field_name_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1307,7 +1319,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 4, SQL_C_DEFAULT, - &field_type, 4, &field_type_len); + &field_type, 4, NULL /* &field_type_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1316,7 +1328,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 5, SQL_C_CHAR, - field_type_name, MAX_INFO_STRING, &field_type_name_len); + field_type_name, MAX_INFO_STRING, NULL /* &field_type_name_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1325,7 +1337,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 6, SQL_C_DEFAULT, - &field_number, MAX_INFO_STRING, &field_number_len); + &field_number, MAX_INFO_STRING, NULL /* &field_number_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1334,7 +1346,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 7, SQL_C_DEFAULT, - &field_length, MAX_INFO_STRING, &field_length_len); + &field_length, MAX_INFO_STRING, NULL /* &field_length_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1343,7 +1355,16 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); } result = SQLBindCol(hcol_stmt, 8, SQL_C_DEFAULT, - &mod_length, MAX_INFO_STRING, &mod_length_len); + &mod_length, MAX_INFO_STRING, NULL /* &mod_length_len */); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 9, SQL_C_CHAR, + ¬_null, MAX_INFO_STRING, NULL /* ¬_null_len */); if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { stmt->errormsg = col_stmt->errormsg; stmt->errornumber = col_stmt->errornumber; @@ -1458,7 +1479,7 @@ mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type)); set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, field_type)); - set_tuplefield_int2(&row->tuple[10], pgtype_nullable(stmt, field_type)); + set_tuplefield_int2(&row->tuple[10], (Int2) (not_null[0] == '1' ? SQL_NO_NULLS : pgtype_nullable(stmt, field_type))); set_tuplefield_string(&row->tuple[11], ""); QR_add_tuple(stmt->result, row); diff --git a/src/interfaces/odbc/lobj.c b/src/interfaces/odbc/lobj.c new file mode 100644 index 0000000000..29cdd2ef04 --- /dev/null +++ b/src/interfaces/odbc/lobj.c @@ -0,0 +1,197 @@ + +/* Module: lobj.c + * + * Description: This module contains routines related to manipulating + * large objects. + * + * Classes: none + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#include "lobj.h" +#include "psqlodbc.h" +#include "connection.h" + +Oid +lo_creat(ConnectionClass *conn, int mode) +{ +LO_ARG argv[1]; +int retval, result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = mode; + + if ( ! CC_send_function(conn, LO_CREAT, &retval, &result_len, 1, argv, 1)) + return 0; // invalid oid + else + return retval; + + +} + +int +lo_open(ConnectionClass *conn, int lobjId, int mode) +{ +int fd; +int result_len; +LO_ARG argv[2]; + + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = lobjId; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = mode; + + if ( ! CC_send_function(conn, LO_OPEN, &fd, &result_len, 1, argv, 2)) + return -1; + + if (fd >= 0 && lo_lseek(conn, fd, 0L, SEEK_SET) < 0) + return -1; + + return fd; +} + +int +lo_close(ConnectionClass *conn, int fd) +{ +LO_ARG argv[1]; +int retval, result_len; + + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + if ( ! CC_send_function(conn, LO_CLOSE, &retval, &result_len, 1, argv, 1)) + return -1; + + else + return retval; + +} + + +int +lo_read(ConnectionClass *conn, int fd, char *buf, int len) +{ +LO_ARG argv[2]; +int result_len; + + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = len; + + if ( ! CC_send_function(conn, LO_READ, (int *) buf, &result_len, 0, argv, 2)) + return -1; + + else + return result_len; +} + +int +lo_write(ConnectionClass *conn, int fd, char *buf, int len) +{ +LO_ARG argv[2]; +int retval, result_len; + + + if (len <= 0) + return 0; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 0; + argv[1].len = len; + argv[1].u.ptr = (char *) buf; + + if ( ! CC_send_function(conn, LO_WRITE, &retval, &result_len, 1, argv, 2)) + return -1; + + else + return retval; +} + +int +lo_lseek(ConnectionClass *conn, int fd, int offset, int whence) +{ +LO_ARG argv[3]; +int retval, result_len; + + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = offset; + + argv[2].isint = 1; + argv[2].len = 4; + argv[2].u.integer = whence; + + if ( ! CC_send_function(conn, LO_LSEEK, &retval, &result_len, 1, argv, 3)) + return -1; + + else + return retval; +} + +int +lo_tell(ConnectionClass *conn, int fd) +{ +LO_ARG argv[1]; +int retval, result_len; + + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + if ( ! CC_send_function(conn, LO_TELL, &retval, &result_len, 1, argv, 1)) + return -1; + + else + return retval; +} + +int +lo_unlink(ConnectionClass *conn, Oid lobjId) +{ +LO_ARG argv[1]; +int retval, result_len; + + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = lobjId; + + if ( ! CC_send_function(conn, LO_UNLINK, &retval, &result_len, 1, argv, 1)) + return -1; + + else + return retval; +} + + + + + + + + diff --git a/src/interfaces/odbc/lobj.h b/src/interfaces/odbc/lobj.h new file mode 100644 index 0000000000..b5ff7e3742 --- /dev/null +++ b/src/interfaces/odbc/lobj.h @@ -0,0 +1,48 @@ + +/* File: lobj.h + * + * Description: See "lobj.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __LOBJ_H__ +#define __LOBJ_H__ + + +#include "psqlodbc.h" + +typedef struct lo_arg { + int isint; + int len; + union + { + int integer; + char *ptr; + } u; +}; + +#define LO_CREAT 957 +#define LO_OPEN 952 +#define LO_CLOSE 953 +#define LO_READ 954 +#define LO_WRITE 955 +#define LO_LSEEK 956 +#define LO_TELL 958 +#define LO_UNLINK 964 + +#define INV_WRITE 0x00020000 +#define INV_READ 0x00040000 + +Oid lo_creat(ConnectionClass *conn, int mode); +int lo_open(ConnectionClass *conn, int lobjId, int mode); +int lo_close(ConnectionClass *conn, int fd); +int lo_read(ConnectionClass *conn, int fd, char *buf, int len); +int lo_write(ConnectionClass *conn, int fd, char *buf, int len); +int lo_lseek(ConnectionClass *conn, int fd, int offset, int len); +int lo_tell(ConnectionClass *conn, int fd); +int lo_unlink(ConnectionClass *conn, Oid lobjId); + +#endif + diff --git a/src/interfaces/odbc/options.c b/src/interfaces/odbc/options.c index 716cfcdfdf..ff9c53200c 100644 --- a/src/interfaces/odbc/options.c +++ b/src/interfaces/odbc/options.c @@ -163,14 +163,28 @@ char changed = FALSE; 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"; + 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); - stmt->scroll_concurrency = SQL_CONCUR_READ_ONLY; - if (vParam != SQL_CONCUR_READ_ONLY) - changed = TRUE; - + 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: @@ -178,25 +192,28 @@ char changed = FALSE; // otherwise, it can only be forward or static. mylog("SetStmtOption(): SQL_CURSOR_TYPE = %d\n", vParam); - if (globals.use_declarefetch) { - stmt->cursor_type = SQL_CURSOR_FORWARD_ONLY; - if (vParam != SQL_CURSOR_FORWARD_ONLY) - changed = TRUE; - } + if (globals.lie) + stmt->cursor_type = vParam; else { - if (vParam == SQL_CURSOR_FORWARD_ONLY || vParam == SQL_CURSOR_STATIC) - stmt->cursor_type = vParam; // valid type + if (globals.use_declarefetch) { + stmt->cursor_type = SQL_CURSOR_FORWARD_ONLY; + if (vParam != SQL_CURSOR_FORWARD_ONLY) + changed = TRUE; + } else { - stmt->cursor_type = SQL_CURSOR_STATIC; - changed = TRUE; + 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"; + stmt->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; return SQL_ERROR; default: @@ -254,6 +271,11 @@ StatementClass *stmt = (StatementClass *) hstmt; *((SDWORD *)pvParam) = stmt->rowset_size; break; + case SQL_KEYSET_SIZE: + mylog("GetStmtOption(): SQL_KEYSET_SIZE\n"); + *((SDWORD *)pvParam) = stmt->keyset_size; + break; + case SQL_CONCURRENCY: mylog("GetStmtOption(): SQL_CONCURRENCY\n"); *((SDWORD *)pvParam) = stmt->scroll_concurrency; @@ -266,7 +288,7 @@ StatementClass *stmt = (StatementClass *) hstmt; case SQL_SIMULATE_CURSOR: stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; - stmt->errormsg = "Simulated positioned update/delete not supported"; + stmt->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; return SQL_ERROR; default: diff --git a/src/interfaces/odbc/psqlodbc.h b/src/interfaces/odbc/psqlodbc.h index 386e369600..07b7ec7b45 100644 --- a/src/interfaces/odbc/psqlodbc.h +++ b/src/interfaces/odbc/psqlodbc.h @@ -88,6 +88,7 @@ typedef struct GlobalValues_ char text_as_longvarchar; char unknowns_as_longvarchar; char bools_as_char; + char lie; char extra_systable_prefixes[MEDIUM_REGISTRY_LEN]; char conn_settings[LARGE_REGISTRY_LEN]; } GLOBAL_VALUES; diff --git a/src/interfaces/odbc/statement.c b/src/interfaces/odbc/statement.c index 7a5b0d886b..bbf1d97818 100644 --- a/src/interfaces/odbc/statement.c +++ b/src/interfaces/odbc/statement.c @@ -151,6 +151,7 @@ StatementClass *rv; 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->errormsg = NULL; diff --git a/src/interfaces/odbc/statement.h b/src/interfaces/odbc/statement.h index 24f58bf050..53ad3894c9 100644 --- a/src/interfaces/odbc/statement.h +++ b/src/interfaces/odbc/statement.h @@ -77,6 +77,7 @@ struct StatementClass_ { int errornumber; int maxRows; int rowset_size; + int keyset_size; int cursor_type; int scroll_concurrency;