From 6df395f63a3f4be10b8f5b81dea1b426021ce5ce Mon Sep 17 00:00:00 2001 From: Hiroshi Inoue Date: Mon, 1 Apr 2002 03:01:15 +0000 Subject: [PATCH] 1) Add rollback functionality to updatable cursors. 2) Implement some options for SQLGetDescField(). 3) Handle *Inifinity* timestamp for SQL_C_CHAR type output. 4) Separate Unicode conversions from common implementations. 5) Improve internal parse_statement() function. --- src/interfaces/odbc/bind.c | 12 +-- src/interfaces/odbc/bind.h | 4 +- src/interfaces/odbc/connection.c | 20 +++- src/interfaces/odbc/connection.h | 4 +- src/interfaces/odbc/convert.c | 33 +++++- src/interfaces/odbc/convert.h | 3 +- src/interfaces/odbc/descriptor.h | 5 +- src/interfaces/odbc/odbcapi30.c | 3 +- src/interfaces/odbc/odbcapi30w.c | 96 ++++++++++++++++-- src/interfaces/odbc/parse.c | 51 ++++++++-- src/interfaces/odbc/pgapi30.c | 168 +++++++++++++++++-------------- src/interfaces/odbc/pgapifunc.h | 4 + src/interfaces/odbc/psqlodbc.h | 2 +- src/interfaces/odbc/qresult.c | 2 + src/interfaces/odbc/qresult.h | 3 +- src/interfaces/odbc/results.c | 167 +++++++++++++++++++++++++----- src/interfaces/odbc/statement.c | 4 +- 17 files changed, 447 insertions(+), 134 deletions(-) diff --git a/src/interfaces/odbc/bind.c b/src/interfaces/odbc/bind.c index 397d984ec6..e931b8b8d5 100644 --- a/src/interfaces/odbc/bind.c +++ b/src/interfaces/odbc/bind.c @@ -67,8 +67,8 @@ PGAPI_BindParameter( opts->parameters[ipar].paramType = fParamType; opts->parameters[ipar].CType = fCType; opts->parameters[ipar].SQLType = fSqlType; - opts->parameters[ipar].precision = cbColDef; - opts->parameters[ipar].scale = ibScale; + opts->parameters[ipar].column_size = cbColDef; + opts->parameters[ipar].decimal_digits = ibScale; /* * If rebinding a parameter that had data-at-exec stuff in it, then @@ -276,10 +276,10 @@ PGAPI_DescribeParam( *pfSqlType = opts->parameters[ipar].SQLType; if (pcbColDef) - *pcbColDef = opts->parameters[ipar].precision; + *pcbColDef = opts->parameters[ipar].column_size; if (pibScale) - *pibScale = opts->parameters[ipar].scale; + *pibScale = opts->parameters[ipar].decimal_digits; if (pfNullable) *pfNullable = pgtype_nullable(stmt, opts->parameters[ipar].paramType); @@ -458,8 +458,8 @@ reset_a_parameter_binding(APDFields *self, int ipar) self->parameters[ipar].EXEC_buffer = NULL; } self->parameters[ipar].SQLType = 0; - self->parameters[ipar].precision = 0; - self->parameters[ipar].scale = 0; + self->parameters[ipar].column_size = 0; + self->parameters[ipar].decimal_digits = 0; self->parameters[ipar].data_at_exec = FALSE; self->parameters[ipar].lobj_oid = 0; } diff --git a/src/interfaces/odbc/bind.h b/src/interfaces/odbc/bind.h index 16d9f84b6e..7eadea92e4 100644 --- a/src/interfaces/odbc/bind.h +++ b/src/interfaces/odbc/bind.h @@ -40,8 +40,8 @@ struct ParameterInfoClass_ Int2 paramType; Int2 CType; Int2 SQLType; - UInt4 precision; - Int2 scale; + UInt4 column_size; + Int2 decimal_digits; Oid lobj_oid; Int4 *EXEC_used; /* amount of data OR the oid of the large * object */ diff --git a/src/interfaces/odbc/connection.c b/src/interfaces/odbc/connection.c index db237b3f95..d6b622a16d 100644 --- a/src/interfaces/odbc/connection.c +++ b/src/interfaces/odbc/connection.c @@ -288,6 +288,7 @@ CC_Constructor() rv->pg_version_minor = 0; rv->ms_jet = 0; rv->unicode = 0; + rv->result_uncommitted = 0; #ifdef MULTIBYTE rv->client_encoding = NULL; rv->server_encoding = NULL; @@ -1110,21 +1111,30 @@ CC_get_error(ConnectionClass *self, int *number, char **message) } -void CC_on_commit(ConnectionClass *conn, BOOL set_no_trans) +void CC_on_commit(ConnectionClass *conn) { if (CC_is_in_trans(conn)) { - if (set_no_trans) - CC_set_no_trans(conn); +#ifdef DRIVER_CURSOR_IMPLEMENT + if (conn->result_uncommitted) + ProcessRollback(conn, FALSE); +#endif /* DRIVER_CURSOR_IMPLEMENT */ + CC_set_no_trans(conn); } + conn->result_uncommitted = 0; } void CC_on_abort(ConnectionClass *conn, BOOL set_no_trans) { if (CC_is_in_trans(conn)) { +#ifdef DRIVER_CURSOR_IMPLEMENT + if (conn->result_uncommitted) + ProcessRollback(conn, TRUE); +#endif /* DRIVER_CURSOR_IMPLEMENT */ if (set_no_trans) CC_set_no_trans(conn); } + conn->result_uncommitted = 0; } /* @@ -1293,11 +1303,11 @@ CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi, UDWORD flag) } } else if (strnicmp(cmdbuffer, "COMMIT", 6) == 0) - CC_on_commit(self, TRUE); + CC_on_commit(self); else if (strnicmp(cmdbuffer, "ROLLBACK", 8) == 0) CC_on_abort(self, TRUE); else if (strnicmp(cmdbuffer, "END", 3) == 0) - CC_on_commit(self, TRUE); + CC_on_commit(self); else if (strnicmp(cmdbuffer, "ABORT", 5) == 0) CC_on_abort(self, TRUE); diff --git a/src/interfaces/odbc/connection.h b/src/interfaces/odbc/connection.h index 67411edd67..5d01dacd21 100644 --- a/src/interfaces/odbc/connection.h +++ b/src/interfaces/odbc/connection.h @@ -272,6 +272,7 @@ struct ConnectionClass_ Int2 pg_version_minor; char ms_jet; char unicode; + char result_uncommitted; #ifdef MULTIBYTE char *client_encoding; char *server_encoding; @@ -318,8 +319,9 @@ void CC_lookup_pg_version(ConnectionClass *conn); void CC_initialize_pg_version(ConnectionClass *conn); void CC_log_error(const char *func, const char *desc, const ConnectionClass *self); int CC_get_max_query_len(const ConnectionClass *self); -void CC_on_commit(ConnectionClass *conn, BOOL set_no_trans); +void CC_on_commit(ConnectionClass *conn); void CC_on_abort(ConnectionClass *conn, BOOL set_no_trans); +void ProcessRollback(ConnectionClass *conn, BOOL undo); /* CC_send_query_options */ #define CLEAR_RESULT_ON_ABORT 1L diff --git a/src/interfaces/odbc/convert.c b/src/interfaces/odbc/convert.c index c0a2706ef6..e4232137fe 100644 --- a/src/interfaces/odbc/convert.c +++ b/src/interfaces/odbc/convert.c @@ -265,6 +265,16 @@ stime2timestamp(const SIMPLE_TIME *st, char *str, BOOL bZone, BOOL precision) int i; precstr[0] = '\0'; + if (st->infinity > 0) + { + strcpy(str, "Infinity"); + return TRUE; + } + else if (st->infinity < 0) + { + strcpy(str, "-Infinity"); + return TRUE; + } if (precision && st->fr) { sprintf(precstr, ".%09d", st->fr); @@ -447,6 +457,27 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 case PG_TYPE_DATETIME: case PG_TYPE_TIMESTAMP: st.fr = 0; + st.infinity = 0; + if (strnicmp(value, "infinity", 8) == 0) + { + st.infinity = 1; + st.m = 12; + st.d = 31; + st.y = 9999; + st.hh = 24; + st.mm = 0; + st.ss = 0; + } + if (strnicmp(value, "-infinity", 9) == 0) + { + st.infinity = -1; + st.m = 0; + st.d = 0; + st.y = 0; + st.hh = 0; + st.mm = 0; + st.ss = 0; + } if (strnicmp(value, "invalid", 7) != 0) { BOOL bZone = (field_type != PG_TYPE_TIMESTAMP_NO_TMZONE && PG_VERSION_GE(SC_get_conn(stmt), 7.2)); @@ -2495,7 +2526,7 @@ convert_money(const char *s, char *sout, size_t soutmax) * It does not zero out SIMPLE_TIME in case it is desired to initialize it with a value */ char -parse_datetime(char *buf, SIMPLE_TIME *st) +parse_datetime(const char *buf, SIMPLE_TIME *st) { int y, m, diff --git a/src/interfaces/odbc/convert.h b/src/interfaces/odbc/convert.h index 2d5f01859f..1ca0b1bb0c 100644 --- a/src/interfaces/odbc/convert.h +++ b/src/interfaces/odbc/convert.h @@ -25,6 +25,7 @@ typedef struct { + int infinity; int m; int d; int y; @@ -42,7 +43,7 @@ int copy_statement_with_parameters(StatementClass *stmt); int convert_escape(const char *value, StatementClass *stmt, int *npos, int *stsize, const char **val_resume); BOOL convert_money(const char *s, char *sout, size_t soutmax); -char parse_datetime(char *buf, SIMPLE_TIME *st); +char parse_datetime(const char *buf, SIMPLE_TIME *st); int convert_linefeeds(const char *s, char *dst, size_t max, BOOL convlf, BOOL *changed); int convert_special_chars(const char *si, char *dst, int used, BOOL convlf,int ccsc); diff --git a/src/interfaces/odbc/descriptor.h b/src/interfaces/odbc/descriptor.h index 58036914ad..5186810b21 100644 --- a/src/interfaces/odbc/descriptor.h +++ b/src/interfaces/odbc/descriptor.h @@ -5,7 +5,7 @@ * * Comments: See "notice.txt" for copyright and license information. * - * $Id: descriptor.h,v 1.1 2002/03/28 08:08:02 inoue Exp $ + * $Id: descriptor.h,v 1.2 2002/04/01 03:01:14 inoue Exp $ * */ @@ -92,5 +92,8 @@ void IRDFields_free(IRDFields *self); void IPDFields_free(IPDFields *self); void ARD_unbind_cols(ARDFields *self, BOOL freeall); void APD_free_params(APDFields *self, char option); +#if (ODBCVER >= 0x0300) +void Desc_set_error(SQLHDESC hdesc, int errornumber, const char * errormsg); +#endif /* ODBCVER */ #endif diff --git a/src/interfaces/odbc/odbcapi30.c b/src/interfaces/odbc/odbcapi30.c index 0e9b63a304..8bcb1cab14 100644 --- a/src/interfaces/odbc/odbcapi30.c +++ b/src/interfaces/odbc/odbcapi30.c @@ -235,7 +235,8 @@ SQLGetDiagField(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT *StringLength) { mylog("[[SQLGetDiagField]] Handle=(%u,%x) Rec=%d Id=%d\n", HandleType, Handle, RecNumber, DiagIdentifier); - return SQL_ERROR; + return PGAPI_GetDiagField(HandleType, Handle, RecNumber, DiagIdentifier, + DiagInfo, BufferLength, StringLength); } /* SQLError -> SQLDiagRec */ diff --git a/src/interfaces/odbc/odbcapi30w.c b/src/interfaces/odbc/odbcapi30w.c index 0273f97b6b..ac0f1d9931 100644 --- a/src/interfaces/odbc/odbcapi30w.c +++ b/src/interfaces/odbc/odbcapi30w.c @@ -89,10 +89,25 @@ SQLSetDescFieldW(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, mylog("[SQLSetDescFieldW]"); if (BufferLength > 0) { - uval = ucs2_to_utf8(Value, BufferLength / 2, &vallen); - val_alloced = TRUE; + switch (FieldIdentifier) + { + case SQL_DESC_BASE_COLUMN_NAME: + case SQL_DESC_BASE_TABLE_NAME: + case SQL_DESC_CATALOG_NAME: + case SQL_DESC_LABEL: + case SQL_DESC_LITERAL_PREFIX: + case SQL_DESC_LITERAL_SUFFIX: + case SQL_DESC_LOCAL_TYPE_NAME: + case SQL_DESC_NAME: + case SQL_DESC_SCHEMA_NAME: + case SQL_DESC_TABLE_NAME: + case SQL_DESC_TYPE_NAME: + uval = ucs2_to_utf8(Value, BufferLength / 2, &vallen); + val_alloced = TRUE; + break; + } } - else + if (!val_alloced) { uval = Value; vallen = BufferLength; @@ -109,11 +124,49 @@ SQLGetDescFieldW(SQLHDESC hdesc, SQLSMALLINT iRecord, SQLSMALLINT iField, SQLINTEGER *pcbValue) { RETCODE ret; - char *qstr = NULL, *mtxt = NULL; + BOOL alloced = FALSE; + SQLINTEGER blen, bMax, *pcbV; + char *rgbV = NULL; mylog("[SQLGetDescFieldW]"); - ret = PGAPI_GetDescField(hdesc, iRecord, iField, rgbValue, - cbValueMax, pcbValue); + switch (iField) + { + case SQL_DESC_BASE_COLUMN_NAME: + case SQL_DESC_BASE_TABLE_NAME: + case SQL_DESC_CATALOG_NAME: + case SQL_DESC_LABEL: + case SQL_DESC_LITERAL_PREFIX: + case SQL_DESC_LITERAL_SUFFIX: + case SQL_DESC_LOCAL_TYPE_NAME: + case SQL_DESC_NAME: + case SQL_DESC_SCHEMA_NAME: + case SQL_DESC_TABLE_NAME: + case SQL_DESC_TYPE_NAME: + alloced = TRUE; + bMax = cbValueMax * 3 / 2; + rgbV = malloc(bMax + 1); + pcbV = &blen; + break; + default: + rgbV = rgbValue; + bMax = cbValueMax; + pcbV = pcbValue; + break; + } + ret = PGAPI_GetDescField(hdesc, iRecord, iField, rgbV, bMax, pcbV); + if (alloced) + { + blen = utf8_to_ucs2(rgbV, blen, (SQLWCHAR *) rgbValue, cbValueMax / 2); + if (SQL_SUCCESS == ret && blen * 2 > cbValueMax) + { + ret = SQL_SUCCESS_WITH_INFO; + Desc_set_error(hdesc, STMT_TRUNCATED, "The buffer was too small for the rgbDesc."); + } + if (pcbValue) + *pcbValue = blen * 2; + free(rgbV); + } + return ret; } @@ -171,6 +224,9 @@ RETCODE SQL_API SQLColAttributeW( SQLINTEGER *pfDesc) { RETCODE ret; + BOOL alloced = FALSE; + SQLSMALLINT *rgbL, blen, bMax; + char *rgbD = NULL; mylog("[SQLColAttributeW]"); switch (fDescType) @@ -187,11 +243,35 @@ RETCODE SQL_API SQLColAttributeW( case SQL_DESC_TABLE_NAME: case SQL_DESC_TYPE_NAME: case SQL_COLUMN_NAME: + alloced = TRUE; + bMax = cbDescMax * 3 / 2; + rgbD = malloc(bMax + 1); + rgbL = &blen; break; + default: + rgbD = rgbDesc; + bMax = cbDescMax; + rgbL = pcbDesc; + break; } - ret = PGAPI_ColAttributes(hstmt, icol, fDescType, rgbDesc, - cbDescMax, pcbDesc, pfDesc); + ret = PGAPI_ColAttributes(hstmt, icol, fDescType, rgbD, + bMax, rgbL, pfDesc); + if (alloced) + { + blen = utf8_to_ucs2(rgbD, blen, (SQLWCHAR *) rgbDesc, cbDescMax / 2); + if (SQL_SUCCESS == ret && blen * 2 > cbDescMax) + { + StatementClass *stmt = (StatementClass *) hstmt; + + ret = SQL_SUCCESS_WITH_INFO; + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "The buffer was too small for the rgbDesc."; + } + if (pcbDesc) + *pcbDesc = blen * 2; + free(rgbD); + } return ret; } diff --git a/src/interfaces/odbc/parse.c b/src/interfaces/odbc/parse.c index 3549ff2ef7..d86e62f85b 100644 --- a/src/interfaces/odbc/parse.c +++ b/src/interfaces/odbc/parse.c @@ -304,7 +304,8 @@ parse_statement(StatementClass *stmt) in_on = FALSE, in_from = FALSE, in_where = FALSE, - in_table = FALSE; + in_table = FALSE, + out_table = TRUE; char in_field = FALSE, in_expr = FALSE, in_func = FALSE, @@ -610,12 +611,21 @@ parse_statement(StatementClass *stmt) if (in_from) { - if (!in_table) + if (token[0] == ';') { - if (!token[0]) + in_from = FALSE; + break; + } + switch (token[0]) + { + case '\0': continue; - if (token[0] == ';') - break; + case ',': + out_table = TRUE; + continue; + } + if (out_table && !in_table) /* new table */ + { if (!(stmt->ntab % TAB_INCR)) { @@ -660,22 +670,47 @@ parse_statement(StatementClass *stmt) mylog("got table = '%s'\n", ti[stmt->ntab]->name); if (delim == ',') + { + out_table = TRUE; mylog("more than 1 tables\n"); + } else + { + out_table = FALSE; in_table = TRUE; + } stmt->ntab++; continue; } - if (token[0] == ';') - break; - if (stricmp(token, "as")) + if (!dquote && stricmp(token, "JOIN") == 0) { + in_table = FALSE; + out_table = TRUE; + continue; + } + if (in_table && stricmp(token, "as")) + { + if (!dquote) + { + if (stricmp(token, "LEFT") == 0 || + stricmp(token, "RIGHT") == 0 || + stricmp(token, "OUTER") == 0 || + stricmp(token, "FULL") == 0 || + stricmp(token, "ON") == 0) + { + in_table = FALSE; + continue; + } + } strcpy(ti[stmt->ntab - 1]->alias, token); mylog("alias for table '%s' is '%s'\n", ti[stmt->ntab - 1]->name, ti[stmt->ntab - 1]->alias); in_table = FALSE; if (delim == ',') + { + out_table = TRUE; mylog("more than 1 tables\n"); + } } } /* in_from */ } diff --git a/src/interfaces/odbc/pgapi30.c b/src/interfaces/odbc/pgapi30.c index a67cc434fa..e737739315 100644 --- a/src/interfaces/odbc/pgapi30.c +++ b/src/interfaces/odbc/pgapi30.c @@ -27,7 +27,7 @@ #include "descriptor.h" #include "pgapifunc.h" -static HSTMT statementHandleFromDescHandle(HSTMT, SQLINTEGER *descType); +static HSTMT statementHandleFromDescHandle(SQLHDESC, SQLINTEGER *descType); /* SQLError -> SQLDiagRec */ RETCODE SQL_API PGAPI_GetDiagRec(SQLSMALLINT HandleType, SQLHANDLE Handle, @@ -75,7 +75,7 @@ PGAPI_GetDiagField(SQLSMALLINT HandleType, SQLHANDLE Handle, PTR DiagInfoPtr, SQLSMALLINT BufferLength, SQLSMALLINT *StringLengthPtr) { - RETCODE ret = SQL_SUCCESS; + RETCODE ret = SQL_ERROR; static const char *func = "PGAPI_GetDiagField"; mylog("%s entering rec=%d", func, RecNumber); @@ -122,6 +122,7 @@ PGAPI_GetDiagField(SQLSMALLINT HandleType, SQLHANDLE Handle, case SQL_DIAG_NUMBER: case SQL_DIAG_RETURNCODE: case SQL_DIAG_SERVER_NAME: + break; case SQL_DIAG_SQLSTATE: break; } @@ -154,7 +155,12 @@ PGAPI_GetConnectAttr(HDBC ConnectionHandle, *((SQLUINTEGER *) Value) = SQL_FALSE; break; case SQL_ATTR_CONNECTION_DEAD: - *((SQLUINTEGER *) Value) = SQL_CD_FALSE; + if (CC_is_in_trans(conn)) + *((SQLUINTEGER *) Value) = SQL_CD_FALSE; + else if (conn->num_stmts > 0) + *((SQLUINTEGER *) Value) = SQL_CD_FALSE; + else + *((SQLUINTEGER *) Value) = SQL_CD_FALSE; break; case SQL_ATTR_CONNECTION_TIMEOUT: *((SQLUINTEGER *) Value) = 0; @@ -172,7 +178,7 @@ PGAPI_GetConnectAttr(HDBC ConnectionHandle, return ret; } -static HSTMT +static SQLHDESC descHandleFromStatementHandle(HSTMT StatementHandle, SQLINTEGER descType) { switch (descType) @@ -189,7 +195,7 @@ descHandleFromStatementHandle(HSTMT StatementHandle, SQLINTEGER descType) return (HSTMT) 0; } static HSTMT -statementHandleFromDescHandle(HSTMT DescHandle, SQLINTEGER *descType) +statementHandleFromDescHandle(SQLHDESC DescHandle, SQLINTEGER *descType) { SQLUINTEGER res = (SQLUINTEGER) DescHandle % 4; if (descType) @@ -209,6 +215,19 @@ statementHandleFromDescHandle(HSTMT DescHandle, SQLINTEGER *descType) return (HSTMT) ((SQLUINTEGER) DescHandle - res); } +void Desc_set_error(SQLHDESC hdesc, int errornumber, const char *errormsg) +{ + SQLINTEGER descType; + HSTMT hstmt = statementHandleFromDescHandle(hdesc, &descType); + StatementClass *stmt; + + if (!hstmt) + return; + stmt = (StatementClass *) hstmt; + stmt->errornumber = errornumber; + stmt->errormsg = errormsg; /* should be static */ +} + static void column_bindings_set(ARDFields *opts, int cols, BOOL maxset) { int i; @@ -568,7 +587,7 @@ IPDSetField(StatementClass *stmt, SQLSMALLINT RecNumber, apdopts->parameters[RecNumber - 1].paramType = (Int2) Value; break; case SQL_DESC_SCALE: - apdopts->parameters[RecNumber - 1].scale = (Int2) Value; + apdopts->parameters[RecNumber - 1].decimal_digits = (Int2) Value; break; case SQL_DESC_ALLOC_TYPE: /* read-only */ case SQL_DESC_CASE_SENSITIVE: /* read-only */ @@ -599,7 +618,7 @@ ARDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, SQLINTEGER *StringLength) { RETCODE ret = SQL_SUCCESS; - SQLINTEGER len, ival; + SQLINTEGER len, ival, rettype = 0; PTR ptr = NULL; const ARDFields *opts = SC_get_ARD(stmt); @@ -610,9 +629,11 @@ ARDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, ival = opts->rowset_size; break; case SQL_DESC_ARRAY_STATUS_PTR: + rettype = SQL_IS_POINTER; ptr = opts->row_operation_ptr; break; case SQL_DESC_BIND_OFFSET_PTR: + rettype = SQL_IS_POINTER; ptr = opts->row_offset_ptr; break; case SQL_DESC_BIND_TYPE: @@ -651,6 +672,7 @@ ARDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, ival = opts->bindings[RecNumber - 1].returntype; break; case SQL_DESC_DATA_PTR: + rettype = SQL_IS_POINTER; if (!RecNumber) ptr = opts->bookmark->buffer; else @@ -659,6 +681,7 @@ ARDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, } break; case SQL_DESC_INDICATOR_PTR: + rettype = SQL_IS_POINTER; if (!RecNumber) ptr = opts->bookmark->used; else @@ -667,6 +690,7 @@ ARDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, } break; case SQL_DESC_OCTET_LENGTH_PTR: + rettype = SQL_IS_POINTER; if (!RecNumber) ptr = opts->bookmark->used; else @@ -694,25 +718,13 @@ ARDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, default:ret = SQL_ERROR; stmt->errornumber = STMT_INVALID_DESCRIPTOR_IDENTIFIER; } - switch (BufferLength) + switch (rettype) { case 0: case SQL_IS_INTEGER: len = 4; *((SQLINTEGER *) Value) = ival; break; - case SQL_IS_UINTEGER: - len = 4; - *((UInt4 *) Value) = ival; - break; - case SQL_IS_SMALLINT: - len = 2; - *((SQLSMALLINT *) Value) = (SQLSMALLINT) ival; - break; - case SQL_IS_USMALLINT: - len = 2; - *((SQLUSMALLINT *) Value) = (SQLUSMALLINT) ival; - break; case SQL_IS_POINTER: len = 4; *((void **) Value) = ptr; @@ -730,7 +742,7 @@ APDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, SQLINTEGER *StringLength) { RETCODE ret = SQL_SUCCESS; - SQLINTEGER ival = 0, len; + SQLINTEGER ival = 0, len, rettype = 0; PTR ptr = NULL; const APDFields *opts = SC_get_APD(stmt); @@ -738,12 +750,15 @@ APDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, switch (FieldIdentifier) { case SQL_DESC_ARRAY_SIZE: + rettype = SQL_IS_POINTER; ival = opts->paramset_size; break; case SQL_DESC_ARRAY_STATUS_PTR: + rettype = SQL_IS_POINTER; ptr = opts->param_operation_ptr; break; case SQL_DESC_BIND_OFFSET_PTR: + rettype = SQL_IS_POINTER; ptr = opts->param_offset_ptr; break; case SQL_DESC_BIND_TYPE: @@ -783,15 +798,18 @@ APDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, ival = opts->parameters[RecNumber - 1].CType; break; case SQL_DESC_DATA_PTR: + rettype = SQL_IS_POINTER; ptr = opts->parameters[RecNumber - 1].buffer; break; case SQL_DESC_INDICATOR_PTR: + rettype = SQL_IS_POINTER; ptr = opts->parameters[RecNumber - 1].used; break; case SQL_DESC_OCTET_LENGTH: ival = opts->parameters[RecNumber - 1].buflen; break; case SQL_DESC_OCTET_LENGTH_PTR: + rettype = SQL_IS_POINTER; ptr = opts->parameters[RecNumber - 1].used; break; case SQL_DESC_COUNT: @@ -800,33 +818,21 @@ APDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, case SQL_DESC_ALLOC_TYPE: /* read-only */ ival = SQL_DESC_ALLOC_AUTO; break; + case SQL_DESC_PRECISION: + case SQL_DESC_SCALE: case SQL_DESC_DATETIME_INTERVAL_PRECISION: case SQL_DESC_LENGTH: case SQL_DESC_NUM_PREC_RADIX: - case SQL_DESC_PRECISION: - case SQL_DESC_SCALE: default:ret = SQL_ERROR; stmt->errornumber = STMT_INVALID_DESCRIPTOR_IDENTIFIER; } - switch (BufferLength) + switch (rettype) { case 0: case SQL_IS_INTEGER: len = 4; *((Int4 *) Value) = ival; break; - case SQL_IS_UINTEGER: - len = 4; - *((UInt4 *) Value) = ival; - break; - case SQL_IS_SMALLINT: - len = 2; - *((SQLSMALLINT *) Value) = (SQLSMALLINT) ival; - break; - case SQL_IS_USMALLINT: - len = 2; - *((SQLUSMALLINT *) Value) = (SQLUSMALLINT) ival; - break; case SQL_IS_POINTER: len = 4; *((void **) Value) = ptr; @@ -844,36 +850,33 @@ IRDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, SQLINTEGER *StringLength) { RETCODE ret = SQL_SUCCESS; - SQLINTEGER ival = 0, len; + SQLINTEGER ival = 0, len, rettype = 0; PTR ptr = NULL; + BOOL bCallColAtt = FALSE; const IRDFields *opts = SC_get_IRD(stmt); switch (FieldIdentifier) { case SQL_DESC_ARRAY_STATUS_PTR: + rettype = SQL_IS_POINTER; ptr = opts->rowStatusArray; break; case SQL_DESC_ROWS_PROCESSED_PTR: + rettype = SQL_IS_POINTER; ptr = opts->rowsFetched; break; case SQL_DESC_ALLOC_TYPE: /* read-only */ + ival = SQL_DESC_ALLOC_AUTO; + break; case SQL_DESC_COUNT: /* read-only */ case SQL_DESC_AUTO_UNIQUE_VALUE: /* read-only */ - case SQL_DESC_BASE_COLUMN_NAME: /* read-only */ - case SQL_DESC_BASE_TABLE_NAME: /* read-only */ case SQL_DESC_CASE_SENSITIVE: /* read-only */ - case SQL_DESC_CATALOG_NAME: /* read-only */ case SQL_DESC_CONCISE_TYPE: /* read-only */ case SQL_DESC_DATETIME_INTERVAL_CODE: /* read-only */ case SQL_DESC_DATETIME_INTERVAL_PRECISION: /* read-only */ case SQL_DESC_DISPLAY_SIZE: /* read-only */ case SQL_DESC_FIXED_PREC_SCALE: /* read-only */ - case SQL_DESC_LABEL: /* read-only */ case SQL_DESC_LENGTH: /* read-only */ - case SQL_DESC_LITERAL_PREFIX: /* read-only */ - case SQL_DESC_LITERAL_SUFFIX: /* read-only */ - case SQL_DESC_LOCAL_TYPE_NAME: /* read-only */ - case SQL_DESC_NAME: /* read-only */ case SQL_DESC_NULLABLE: /* read-only */ case SQL_DESC_NUM_PREC_RADIX: /* read-only */ case SQL_DESC_OCTET_LENGTH: /* read-only */ @@ -882,18 +885,40 @@ IRDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, case SQL_DESC_ROWVER: /* read-only */ #endif /* ODBCVER */ case SQL_DESC_SCALE: /* read-only */ - case SQL_DESC_SCHEMA_NAME: /* read-only */ case SQL_DESC_SEARCHABLE: /* read-only */ - case SQL_DESC_TABLE_NAME: /* read-only */ case SQL_DESC_TYPE: /* read-only */ - case SQL_DESC_TYPE_NAME: /* read-only */ case SQL_DESC_UNNAMED: /* read-only */ case SQL_DESC_UNSIGNED: /* read-only */ case SQL_DESC_UPDATABLE: /* read-only */ + bCallColAtt = TRUE; + break; + case SQL_DESC_BASE_COLUMN_NAME: /* read-only */ + case SQL_DESC_BASE_TABLE_NAME: /* read-only */ + case SQL_DESC_CATALOG_NAME: /* read-only */ + case SQL_DESC_LABEL: /* read-only */ + case SQL_DESC_LITERAL_PREFIX: /* read-only */ + case SQL_DESC_LITERAL_SUFFIX: /* read-only */ + case SQL_DESC_LOCAL_TYPE_NAME: /* read-only */ + case SQL_DESC_NAME: /* read-only */ + case SQL_DESC_SCHEMA_NAME: /* read-only */ + case SQL_DESC_TABLE_NAME: /* read-only */ + case SQL_DESC_TYPE_NAME: /* read-only */ + rettype = SQL_NTS; + bCallColAtt = TRUE; + break; default:ret = SQL_ERROR; stmt->errornumber = STMT_INVALID_DESCRIPTOR_IDENTIFIER; } - switch (BufferLength) + if (bCallColAtt) + { + SQLSMALLINT pcbL; + + ret = PGAPI_ColAttributes(stmt, RecNumber, + FieldIdentifier, Value, (SQLSMALLINT) BufferLength, + &pcbL, &ival); + len = pcbL; + } + switch (rettype) { case 0: case SQL_IS_INTEGER: @@ -904,14 +929,6 @@ IRDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, len = 4; *((UInt4 *) Value) = ival; break; - case SQL_IS_SMALLINT: - len = 2; - *((SQLSMALLINT *) Value) = (SQLSMALLINT) ival; - break; - case SQL_IS_USMALLINT: - len = 2; - *((SQLUSMALLINT *) Value) = (SQLUSMALLINT) ival; - break; case SQL_IS_POINTER: len = 4; *((void **) Value) = ptr; @@ -929,7 +946,7 @@ IPDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, SQLINTEGER *StringLength) { RETCODE ret = SQL_SUCCESS; - SQLINTEGER ival = 0, len; + SQLINTEGER ival = 0, len, rettype = 0; PTR ptr = NULL; const IPDFields *ipdopts = SC_get_IPD(stmt); const APDFields *apdopts = SC_get_APD(stmt); @@ -937,9 +954,11 @@ IPDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, switch (FieldIdentifier) { case SQL_DESC_ARRAY_STATUS_PTR: + rettype = SQL_IS_POINTER; ptr = ipdopts->param_status_ptr; break; case SQL_DESC_ROWS_PROCESSED_PTR: + rettype = SQL_IS_POINTER; ptr = ipdopts->param_processed_ptr; break; case SQL_DESC_UNNAMED: /* only SQL_UNNAMED is allowed */ @@ -981,8 +1000,24 @@ IPDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, case SQL_DESC_PARAMETER_TYPE: ival = apdopts->parameters[RecNumber - 1].paramType; break; + case SQL_DESC_PRECISION: + switch (apdopts->parameters[RecNumber - 1].CType) + { + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_DATETIME: + ival = apdopts->parameters[RecNumber - 1].decimal_digits; + break; + } + break; case SQL_DESC_SCALE: - ival = apdopts->parameters[RecNumber - 1].scale ; + switch (apdopts->parameters[RecNumber - 1].CType) + { + case SQL_C_NUMERIC: + ival = apdopts->parameters[RecNumber - 1].decimal_digits; + break; + } break; case SQL_DESC_ALLOC_TYPE: /* read-only */ ival = SQL_DESC_ALLOC_AUTO; @@ -996,7 +1031,6 @@ IPDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, case SQL_DESC_NULLABLE: /* read-only */ case SQL_DESC_NUM_PREC_RADIX: case SQL_DESC_OCTET_LENGTH: - case SQL_DESC_PRECISION: #if (ODBCVER >= 0x0350) case SQL_DESC_ROWVER: /* read-only */ #endif /* ODBCVER */ @@ -1005,25 +1039,13 @@ IPDGetField(StatementClass *stmt, SQLSMALLINT RecNumber, default:ret = SQL_ERROR; stmt->errornumber = STMT_INVALID_DESCRIPTOR_IDENTIFIER; } - switch (BufferLength) + switch (rettype) { case 0: case SQL_IS_INTEGER: len = 4; *((Int4 *) Value) = ival; break; - case SQL_IS_UINTEGER: - len = 4; - *((UInt4 *) Value) = ival; - break; - case SQL_IS_SMALLINT: - len = 2; - *((SQLSMALLINT *) Value) = (SQLSMALLINT) ival; - break; - case SQL_IS_USMALLINT: - len = 2; - *((SQLUSMALLINT *) Value) = (SQLUSMALLINT) ival; - break; case SQL_IS_POINTER: len = 4; *((void **)Value) = ptr; diff --git a/src/interfaces/odbc/pgapifunc.h b/src/interfaces/odbc/pgapifunc.h index a906a311de..f4c740bca8 100644 --- a/src/interfaces/odbc/pgapifunc.h +++ b/src/interfaces/odbc/pgapifunc.h @@ -265,6 +265,10 @@ RETCODE SQL_API PGAPI_GetDiagRec(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT RecNumber, SQLCHAR *Sqlstate, SQLINTEGER *NativeError, SQLCHAR *MessageText, SQLSMALLINT BufferLength, SQLSMALLINT *TextLength); +RETCODE SQL_API PGAPI_GetDiagField(SQLSMALLINT HandleType, SQLHANDLE Handle, + SQLSMALLINT RecNumber, SQLSMALLINT DiagIdentifier, + PTR DiagInfoPtr, SQLSMALLINT BufferLength, + SQLSMALLINT *StringLengthPtr); RETCODE SQL_API PGAPI_GetConnectAttr(HDBC ConnectionHandle, SQLINTEGER Attribute, PTR Value, SQLINTEGER BufferLength, SQLINTEGER *StringLength); diff --git a/src/interfaces/odbc/psqlodbc.h b/src/interfaces/odbc/psqlodbc.h index a0d4be134b..839c6e4876 100644 --- a/src/interfaces/odbc/psqlodbc.h +++ b/src/interfaces/odbc/psqlodbc.h @@ -5,7 +5,7 @@ * * Comments: See "notice.txt" for copyright and license information. * - * $Id: psqlodbc.h,v 1.61 2002/03/28 08:08:06 inoue Exp $ + * $Id: psqlodbc.h,v 1.62 2002/04/01 03:01:15 inoue Exp $ * */ diff --git a/src/interfaces/odbc/qresult.c b/src/interfaces/odbc/qresult.c index 923448abeb..2817a7f316 100644 --- a/src/interfaces/odbc/qresult.c +++ b/src/interfaces/odbc/qresult.c @@ -123,6 +123,7 @@ QR_Constructor() rv->rowset_size = 1; rv->haskeyset = 0; rv->keyset = NULL; + rv->rb_alloc = 0; rv->rb_count = 0; rv->rollback = NULL; } @@ -233,6 +234,7 @@ QR_free_memory(QResultClass *self) if (self->rollback) { free(self->rollback); + self->rb_alloc = 0; self->rb_count = 0; self->rollback = NULL; } diff --git a/src/interfaces/odbc/qresult.h b/src/interfaces/odbc/qresult.h index a7291c43a6..1bf1ce93b7 100644 --- a/src/interfaces/odbc/qresult.h +++ b/src/interfaces/odbc/qresult.h @@ -74,7 +74,8 @@ struct QResultClass_ char aborted; /* was aborted? */ char haskeyset; /* this result contains keyset ? */ KeySet *keyset; - UInt4 rb_count; /* count of rollback info */ + UInt2 rb_alloc; /* count of allocated rollback info */ + UInt2 rb_count; /* count of rollback info */ Rollback *rollback; }; diff --git a/src/interfaces/odbc/results.c b/src/interfaces/odbc/results.c index 6d20bbaad5..517d5a234d 100644 --- a/src/interfaces/odbc/results.c +++ b/src/interfaces/odbc/results.c @@ -491,7 +491,7 @@ PGAPI_ColAttributes( return SQL_SUCCESS; } - if (stmt->parse_status != STMT_PARSE_FATAL && irdflds->fi && irdflds->fi[col_idx]) + if (stmt->parse_status != STMT_PARSE_FATAL && irdflds->fi) { if (col_idx >= cols) { @@ -500,9 +500,12 @@ PGAPI_ColAttributes( SC_log_error(func, "", stmt); return SQL_ERROR; } - field_type = irdflds->fi[col_idx]->type; - if (field_type > 0) - parse_ok = TRUE; + if (irdflds->fi[col_idx]) + { + field_type = irdflds->fi[col_idx]->type; + if (field_type > 0) + parse_ok = TRUE; + } } } @@ -756,14 +759,6 @@ inolog("COLUMN_TYPE=%d\n", value); if (rgbDesc) { -#ifdef UNICODE_SUPPORT - if (conn->unicode) - { - len = utf8_to_ucs2(p, len, (SQLWCHAR *) rgbDesc, cbDescMax / 2); - len *= 2; - } - else -#endif /* UNICODE_SUPPORT */ strncpy_null((char *) rgbDesc, p, (size_t) cbDescMax); if (len >= cbDescMax) @@ -1343,8 +1338,6 @@ PGAPI_ExtendedFetch( #endif /* DRIVER_CURSOR_IMPLEMENT */ else *(rgfRowStatus + i) = SQL_ROW_SUCCESS; -if (rgfRowStatus[i] != SQL_ROW_SUCCESS) -inolog("rgfRowStatus[%d]=%d\n", i, rgfRowStatus[i]); } } @@ -1426,6 +1419,121 @@ static void KeySetSet(const TupleField *tuple, int num_fields, KeySet *keyset) sscanf(tuple[num_fields - 1].value, "%u", &keyset->oid); } +static void AddRollback(ConnectionClass *conn, QResultClass *res, int index, const KeySet *keyset) +{ + Rollback *rollback; + + if (!res->rollback) + { + res->rb_count = 0; + res->rb_alloc = 10; + rollback = res->rollback = malloc(sizeof(Rollback) * res->rb_alloc); + } + else + { + if (res->rb_count >= res->rb_alloc) + { + res->rb_alloc *= 2; + if (rollback = realloc(res->rollback, sizeof(Rollback) * res->rb_alloc), !rollback) + { + res->rb_alloc = res->rb_count = 0; + return; + } + res->rollback = rollback; + } + rollback = res->rollback + res->rb_count; + } + rollback->index = index; + if (keyset) + { + rollback->blocknum = keyset[index].blocknum; + rollback->offset = keyset[index].offset; + } + else + { + rollback->offset = 0; + rollback->blocknum = 0; + } + + conn->result_uncommitted = 1; + res->rb_count++; +} + +static void DiscardRollback(QResultClass *res) +{ + int i, index; + UWORD status; + Rollback *rollback; + KeySet *keyset; + + if (0 == res->rb_count || NULL == res->rollback) + return; + rollback = res->rollback; + keyset = res->keyset; + for (i = 0; i < res->rb_count; i++) + { + index = rollback[i].index; + status = keyset[index].status; + keyset[index].status &= ~(CURS_SELF_DELETING | CURS_SELF_UPDATING | CURS_SELF_ADDING); + keyset[index].status |= ((status & (CURS_SELF_DELETING | CURS_SELF_UPDATING | CURS_SELF_ADDING)) << 3); + } + free(rollback); + res->rollback = NULL; + res->rb_count = res->rb_alloc = 0; +} + +static void UndoRollback(QResultClass *res) +{ + int i, index; + UWORD status; + Rollback *rollback; + KeySet *keyset; + + if (0 == res->rb_count || NULL == res->rollback) + return; + rollback = res->rollback; + keyset = res->keyset; + for (i = res->rb_count - 1; i >= 0; i--) + { + index = rollback[i].index; + status = keyset[index].status; + if ((status & CURS_SELF_ADDING) != 0) + { + if (index < res->fcount) + res->fcount = index; + } + else + { + keyset[index].status &= ~(CURS_SELF_DELETING | CURS_SELF_UPDATING | CURS_SELF_ADDING | KEYSET_INFO_PUBLIC); + keyset[index].blocknum = rollback[i].blocknum; + keyset[index].offset = rollback[i].offset; + } + } + free(rollback); + res->rollback = NULL; + res->rb_count = res->rb_alloc = 0; +} + +void ProcessRollback(ConnectionClass *conn, BOOL undo) +{ + int i; + StatementClass *stmt; + QResultClass *res; + + for (i = 0; i < conn->num_stmts; i++) + { + if (stmt = conn->stmts[i], !stmt) + continue; + for (res = SC_get_Result(stmt); res; res = res->next) + { + if (undo) + UndoRollback(res); + else + DiscardRollback(res); + } + } +} + #define LATEST_TUPLE_LOAD 1L #define USE_INSERTED_TID (1L << 1) static QResultClass * @@ -1534,7 +1642,8 @@ SC_pos_reload(StatementClass *stmt, UWORD irow, UDWORD global_ridx, UWORD *count ret = SQL_SUCCESS_WITH_INFO; if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN) { - res->keyset[global_ridx].oid = 0; + res->keyset[global_ridx].blocknum = 0; + res->keyset[global_ridx].offset = 0; res->keyset[global_ridx].status |= SQL_ROW_DELETED; } } @@ -1670,6 +1779,7 @@ SC_pos_update(StatementClass *stmt, num_cols, upd_cols; QResultClass *res; + ConnectionClass *conn = SC_get_conn(stmt); ARDFields *opts = SC_get_ARD(stmt); IRDFields *irdflds = SC_get_IRD(stmt); BindInfoClass *bindings = opts->bindings; @@ -1735,7 +1845,7 @@ SC_pos_update(StatementClass *stmt, sprintf(updstr, "%s where ctid = '(%u, %u)' and oid = %u", updstr, blocknum, pgoffset, oid); mylog("updstr=%s\n", updstr); - if (PGAPI_AllocStmt(SC_get_conn(stmt), &hstmt) != SQL_SUCCESS) + if (PGAPI_AllocStmt(conn, &hstmt) != SQL_SUCCESS) return SQL_ERROR; qstmt = (StatementClass *) hstmt; apdopts = SC_get_APD(qstmt); @@ -1788,8 +1898,11 @@ SC_pos_update(StatementClass *stmt, } if (SQL_SUCCESS == ret && res->keyset) { - if (CC_is_in_trans(SC_get_conn(stmt))) + if (CC_is_in_trans(conn)) + { + AddRollback(conn, res, global_ridx, res->keyset); res->keyset[global_ridx].status |= (SQL_ROW_UPDATED | CURS_SELF_UPDATING); + } else res->keyset[global_ridx].status |= (SQL_ROW_UPDATED | CURS_SELF_UPDATED); } @@ -1815,12 +1928,12 @@ SC_pos_delete(StatementClass *stmt, { UWORD offset; QResultClass *res, *qres; + ConnectionClass *conn = SC_get_conn(stmt); ARDFields *opts = SC_get_ARD(stmt); IRDFields *irdflds = SC_get_IRD(stmt); BindInfoClass *bindings = opts->bindings; char dltstr[4096]; RETCODE ret; - /*const char *oidval;*/ UInt4 oid, blocknum; mylog("POS DELETE ti=%x\n", stmt->ti); @@ -1844,7 +1957,7 @@ SC_pos_delete(StatementClass *stmt, stmt->ti[0]->name, blocknum, offset, oid); mylog("dltstr=%s\n", dltstr); - qres = CC_send_query(SC_get_conn(stmt), dltstr, NULL, CLEAR_RESULT_ON_ABORT); + qres = CC_send_query(conn, dltstr, NULL, CLEAR_RESULT_ON_ABORT); ret = SQL_SUCCESS; if (qres && QR_command_successful(qres)) { @@ -1881,8 +1994,11 @@ SC_pos_delete(StatementClass *stmt, QR_Destructor(qres); if (SQL_SUCCESS == ret && res->keyset) { - if (CC_is_in_trans(SC_get_conn(stmt))) + if (CC_is_in_trans(conn)) + { + AddRollback(conn, res, global_ridx, res->keyset); res->keyset[global_ridx].status |= (SQL_ROW_DELETED | CURS_SELF_DELETING); + } else res->keyset[global_ridx].status |= (SQL_ROW_DELETED | CURS_SELF_DELETED); } @@ -1988,7 +2104,7 @@ SC_pos_add(StatementClass *stmt, num_cols = irdflds->nfields; conn = SC_get_conn(stmt); sprintf(addstr, "insert into \"%s\" (", stmt->ti[0]->name); - if (PGAPI_AllocStmt(SC_get_conn(stmt), &hstmt) != SQL_SUCCESS) + if (PGAPI_AllocStmt(conn, &hstmt) != SQL_SUCCESS) return SQL_ERROR; if (opts->row_offset_ptr) offset = *opts->row_offset_ptr; @@ -2068,10 +2184,15 @@ SC_pos_add(StatementClass *stmt, PGAPI_FreeStmt(hstmt, SQL_DROP); if (SQL_SUCCESS == ret && res->keyset) { + int global_ridx = res->fcount - 1; if (CC_is_in_trans(conn)) - res->keyset[res->fcount - 1].status |= (SQL_ROW_ADDED | CURS_SELF_ADDING); + { + + AddRollback(conn, res, global_ridx, NULL); + res->keyset[global_ridx].status |= (SQL_ROW_ADDED | CURS_SELF_ADDING); + } else - res->keyset[res->fcount - 1].status |= (SQL_ROW_ADDED | CURS_SELF_ADDED); + res->keyset[global_ridx].status |= (SQL_ROW_ADDED | CURS_SELF_ADDED); } #if (ODBCVER >= 0x0300) if (irdflds->rowStatusArray) diff --git a/src/interfaces/odbc/statement.c b/src/interfaces/odbc/statement.c index f48a172421..6bdac48087 100644 --- a/src/interfaces/odbc/statement.c +++ b/src/interfaces/odbc/statement.c @@ -503,8 +503,8 @@ SC_recycle_statement(StatementClass *self) int i; for (i = 0; i < self->ntab; i++) - if (self->ti) - free(self->ti); + if (self->ti[i]) + free(self->ti[i]); self->ti = NULL; self->ntab = 0; }