Index: driver/driver.h =================================================================== --- driver/driver.h (revision 977) +++ driver/driver.h (working copy) @@ -159,7 +159,7 @@ typedef enum { DESC_IMP, DESC_APP } desc_ref_type; /* parameter or row descriptor? */ -typedef enum { DESC_PARAM, DESC_ROW } desc_desc_type; +typedef enum { DESC_PARAM, DESC_ROW, DESC_UNKNOWN } desc_desc_type; /* header or record field? (location in descriptor) */ typedef enum { DESC_HDR, DESC_REC } fld_loc; @@ -196,6 +196,10 @@ /* check if ARD record is a bound column */ #define ARD_IS_BOUND(d) ((d)->data_ptr || (d)->octet_length_ptr) +/* get the dbc from a descriptor */ +#define DESC_GET_DBC(X) (((X)->alloc_type == SQL_DESC_ALLOC_USER) ? \ + (X)->exp.dbc : (X)->stmt->dbc) + typedef struct { int perms; SQLSMALLINT data_type; /* SQL_IS_SMALLINT, etc */ @@ -205,6 +209,7 @@ /* descriptor */ struct tagSTMT; +struct tagDBC; typedef struct { /* header fields */ SQLSMALLINT alloc_type; @@ -228,6 +233,17 @@ DYNAMIC_ARRAY records; MYERROR error; struct tagSTMT *stmt; + + /* SQL_DESC_ALLOC_USER-specific */ + struct { + /* + We keep a list of all statements we've been set on because we need + to put the implicit descriptor back if this one is freed. + */ + LIST *stmts; + /* connection we were allocated on */ + struct tagDBC *dbc; + } exp; } DESC; /* descriptor record */ @@ -322,7 +338,7 @@ ENV *env; MYSQL mysql; LIST *statements; - LIST *descriptors; + LIST *exp_desc; /* explicit descriptors */ LIST list; STMT_OPTIONS stmt_options; MYERROR error; @@ -417,6 +433,9 @@ DESC *ird; DESC *apd; DESC *ipd; + /* implicit descriptors */ + DESC *imp_ard; + DESC *imp_apd; } STMT; Index: driver/driver.def =================================================================== --- driver/driver.def (revision 977) +++ driver/driver.def (working copy) @@ -43,6 +43,7 @@ SQLCancel SQLColAttributes SQLConnect +SQLCopyDesc SQLDescribeCol SQLDisconnect SQLError @@ -150,3 +151,4 @@ + Index: driver/desc.c =================================================================== --- driver/desc.c (revision 977) +++ driver/desc.c (working copy) @@ -77,11 +77,45 @@ desc->bind_type= SQL_BIND_BY_COLUMN; desc->count= 0; desc->rows_processed_ptr= NULL; + desc->exp.stmts= NULL; return desc; } /* + Free a descriptor. +*/ +void desc_free(DESC *desc) +{ + assert(desc); + if (IS_APD(desc)) + desc_free_paramdata(desc); + delete_dynamic(&desc->records); + x_free(desc); +} + + +/* + Free any memory allocated for SQLPutData(). This is only useful + for APDs. +*/ +void desc_free_paramdata(DESC *desc) +{ + SQLLEN i; + for (i= 0; i < desc->count; i++) + { + DESCREC *aprec= desc_get_rec(desc, i, FALSE); + assert(aprec); + if (aprec->par.alloced) + { + aprec->par.alloced= FALSE; + my_free(aprec->par.value, MYF(0)); + } + } +} + + +/* * Initialize APD */ void desc_rec_init_apd(DESCREC *rec) @@ -212,6 +246,33 @@ /* + * Disassociate a statement from an explicitly allocated + * descriptor. + * + * @param desc The descriptor + * @param stmt The statement + */ +void desc_remove_stmt(DESC *desc, STMT *stmt) +{ + LIST *lstmt; + + if (desc->alloc_type != SQL_DESC_ALLOC_USER) + return; + + for (lstmt= desc->exp.stmts; lstmt; lstmt= lstmt->next) + { + if (lstmt->data == stmt) + { + desc->exp.stmts= list_delete(desc->exp.stmts, lstmt); + return; + } + } + + assert(!"Statement was not associated with descriptor"); +} + + +/* * Apply the actual value to the descriptor field. * * @param dest Pointer to descriptor field to be set. @@ -424,7 +485,7 @@ */ SQLRETURN MySQLGetDescField(SQLHDESC hdesc, SQLSMALLINT recnum, SQLSMALLINT fldid, - SQLPOINTER valptr, SQLINTEGER buflen, SQLINTEGER *strlen) + SQLPOINTER valptr, SQLINTEGER buflen, SQLINTEGER *outlen) { desc_field *fld= getfield(fldid); DESC *desc= (DESC *)hdesc; @@ -722,7 +783,7 @@ apply_desc_val(dest, fld->data_type, val, buflen); /* post-set responsibilities */ - if (IS_ARD(desc) || IS_APD(desc)) + if ((IS_ARD(desc) || IS_APD(desc)) && fld->loc == DESC_REC) { DESCREC *rec= (DESCREC *) dest_struct; switch (fldid) @@ -764,7 +825,7 @@ Set "real_param_done" for parameters if all fields needed to bind a parameter are set. */ - if (IS_APD(desc) && val != NULL) + if (IS_APD(desc) && val != NULL && fld->loc == DESC_REC) { DESCREC *rec= (DESCREC *) dest_struct; switch (fldid) @@ -783,15 +844,13 @@ /* @type : ODBC 3.0 API - @purpose : Set a field of a descriptor. + @purpose : Copy descriptor information from one descriptor to another. Errors are placed in the TargetDescHandle. */ SQLRETURN MySQLCopyDesc(SQLHDESC SourceDescHandle, SQLHDESC TargetDescHandle) { DESC *src= (DESC *)SourceDescHandle; DESC *dest= (DESC *)TargetDescHandle; - SQLSMALLINT alloc_type; - STMT *stmt; CLEAR_DESC_ERROR(dest); @@ -800,18 +859,13 @@ "Cannot modify an implementation row descriptor", MYERR_S1016); - /* we save these because they shouldn't be overwritten */ - alloc_type= dest->alloc_type; - stmt= dest->stmt; - if (IS_IRD(src) && src->stmt->state < ST_PREPARED) return set_desc_error(dest, "HY007", "Associated statement is not prepared", MYERR_S1007); - memcpy(dest, src, sizeof(DESC)); - /* copy the records */ + delete_dynamic(&dest->records); if (my_init_dynamic_array(&dest->records, sizeof(DESCREC), src->records.max_element, src->records.alloc_increment)) @@ -823,9 +877,14 @@ memcpy(dest->records.buffer, src->records.buffer, src->records.max_element * src->records.size_of_element); - /* restore needed fields */ - dest->alloc_type= alloc_type; - dest->stmt= stmt; + /* copy all fields */ + dest->array_size= src->array_size; + dest->array_status_ptr= src->array_status_ptr; + dest->bind_offset_ptr= src->bind_offset_ptr; + dest->bind_type= src->bind_type; + dest->count= src->count; + dest->rows_processed_ptr= src->rows_processed_ptr; + memcpy(&dest->error, &src->error, sizeof(MYERROR)); /* TODO consistency check on target, if needed (apd) */ @@ -840,11 +899,11 @@ SQLRETURN stmt_SQLGetDescField(STMT *stmt, DESC *desc, SQLSMALLINT recnum, SQLSMALLINT fldid, SQLPOINTER valptr, - SQLINTEGER buflen, SQLINTEGER *strlen) + SQLINTEGER buflen, SQLINTEGER *outlen) { SQLRETURN rc; if ((rc= MySQLGetDescField((SQLHANDLE)desc, recnum, fldid, - valptr, buflen, strlen)) != SQL_SUCCESS) + valptr, buflen, outlen)) != SQL_SUCCESS) memcpy(&stmt->error, &desc->error, sizeof(MYERROR)); return rc; } @@ -885,3 +944,4 @@ return MySQLCopyDesc(SourceDescHandle, TargetDescHandle); } + Index: driver/unicode.c =================================================================== --- driver/unicode.c (revision 983) +++ driver/unicode.c (working copy) @@ -513,7 +513,7 @@ dbc= ((STMT *)handle)->dbc; break; case SQL_HANDLE_DESC: - dbc= ((DESC *)handle)->stmt->dbc; + dbc= DESC_GET_DBC((DESC *)handle); break; case SQL_HANDLE_ENV: default: @@ -583,7 +583,7 @@ dbc= ((STMT *)handle)->dbc; break; case SQL_HANDLE_DESC: - dbc= ((DESC *)handle)->stmt->dbc; + dbc= DESC_GET_DBC((DESC *)handle); break; case SQL_HANDLE_ENV: default: @@ -1121,3 +1121,4 @@ #endif + Index: driver/myutil.h =================================================================== --- driver/myutil.h (revision 977) +++ driver/myutil.h (working copy) @@ -250,8 +250,11 @@ DESC *desc_alloc(STMT *stmt, SQLSMALLINT alloc_type, desc_ref_type ref_type, desc_desc_type desc_type); +void desc_free_paramdata(DESC *desc); +void desc_free(DESC *desc); void desc_rec_init_apd(DESCREC *rec); void desc_rec_init_ipd(DESCREC *rec); +void desc_remove_stmt(DESC *desc, STMT *stmt); SQLRETURN stmt_SQLSetDescField(STMT *stmt, DESC *desc, SQLSMALLINT recnum, SQLSMALLINT fldid, SQLPOINTER val, SQLINTEGER buflen); Index: driver/ansi.c =================================================================== --- driver/ansi.c (revision 983) +++ driver/ansi.c (working copy) @@ -658,7 +658,7 @@ dbc= ((STMT *)handle)->dbc; break; case SQL_HANDLE_DESC: - dbc= ((DESC *)handle)->stmt->dbc; + dbc= DESC_GET_DBC((DESC *)handle); break; case SQL_HANDLE_ENV: default: @@ -729,7 +729,7 @@ dbc= ((STMT *)handle)->dbc; break; case SQL_HANDLE_DESC: - dbc= ((DESC *)handle)->stmt->dbc; + dbc= DESC_GET_DBC((DESC *)handle); break; case SQL_HANDLE_ENV: default: Index: driver/handle.c =================================================================== --- driver/handle.c (revision 977) +++ driver/handle.c (working copy) @@ -215,6 +215,7 @@ dbc->list.data= dbc; dbc->unicode= 0; dbc->ansi_charset_info= dbc->cxn_charset_info= NULL; + dbc->exp_desc= NULL; pthread_mutex_init(&dbc->lock,NULL); pthread_mutex_lock(&dbc->lock); myodbc_ov_init(penv->odbc_ver); /* Initialize based on ODBC version */ @@ -244,6 +245,8 @@ SQLRETURN SQL_API my_SQLFreeConnect(SQLHDBC hdbc) { DBC FAR *dbc= (DBC FAR*) hdbc; + LIST *ldesc; + LIST *next; dbc->env->connections= list_delete(dbc->env->connections,&dbc->list); my_free(dbc->dsn,MYF(MY_ALLOW_ZERO_PTR)); @@ -254,6 +257,14 @@ my_free(dbc->password,MYF(MY_ALLOW_ZERO_PTR)); pthread_mutex_destroy(&dbc->lock); + /* free any remaining explicitly allocated descriptors */ + for (ldesc= dbc->exp_desc; ldesc; ldesc= next) + { + next= ldesc->next; + desc_free((DESC *) ldesc->data); + x_free(ldesc); + } + #ifndef _UNIX_ GlobalUnlock(GlobalHandle((HGLOBAL) hdbc)); GlobalFree(GlobalHandle((HGLOBAL) hdbc)); @@ -323,13 +334,15 @@ if (!(stmt->ipd= desc_alloc(stmt, SQL_DESC_ALLOC_AUTO, DESC_IMP, DESC_PARAM))) goto error; + stmt->imp_ard= stmt->ard; + stmt->imp_apd= stmt->apd; return SQL_SUCCESS; error: - x_free(stmt->ard); - x_free(stmt->ird); - x_free(stmt->apd); - x_free(stmt->ipd); + x_free(stmt->ard); + x_free(stmt->ird); + x_free(stmt->apd); + x_free(stmt->ipd); return set_dbc_error(dbc, "HY001", "Memory allocation error", MYERR_S1001); } @@ -391,16 +404,9 @@ stmt->ard->count= 0; return SQL_SUCCESS; } - /* free any allocated memory from SQLPutData() */ - for (i= 0; i < (uint) stmt->apd->count; i++) - { - DESCREC *aprec= desc_get_rec(stmt->apd, i, FALSE); - if (aprec->par.alloced) - { - aprec->par.alloced= FALSE; - my_free(aprec->par.value,MYF(0)); - } - } + + desc_free_paramdata(stmt->apd); + if (fOption == SQL_RESET_PARAMS) { /* remove all params and reset count to 0 (per spec) */ @@ -463,9 +469,6 @@ stmt->query= stmt->orig_query= 0; stmt->param_count= 0; - if (fOption == MYSQL_RESET) - return SQL_SUCCESS; - reset_ptr(stmt->apd->rows_processed_ptr); reset_ptr(stmt->ard->rows_processed_ptr); reset_ptr(stmt->ipd->array_status_ptr); @@ -473,16 +476,18 @@ reset_ptr(stmt->apd->array_status_ptr); reset_ptr(stmt->ard->array_status_ptr); reset_ptr(stmt->stmt_options.rowStatusPtr_ex); - /* TODO what else to reset? */ - delete_dynamic(&stmt->apd->records); - delete_dynamic(&stmt->ipd->records); - delete_dynamic(&stmt->ard->records); - delete_dynamic(&stmt->ird->records); - x_free(stmt->apd); - x_free(stmt->ard); - x_free(stmt->ipd); - x_free(stmt->ird); + if (fOption == MYSQL_RESET) + return SQL_SUCCESS; + + /* explicitly allocated descriptors are affected up until this point */ + desc_remove_stmt(stmt->apd, stmt); + desc_remove_stmt(stmt->ard, stmt); + desc_free(stmt->imp_apd); + desc_free(stmt->imp_ard); + desc_free(stmt->ipd); + desc_free(stmt->ird); + x_free(stmt->cursor.name); delete_dynamic(&stmt->param_pos); @@ -498,6 +503,76 @@ /* + Explicitly allocate a descriptor. +*/ +SQLRETURN my_SQLAllocDesc(SQLHDBC hdbc, SQLHANDLE *pdesc) +{ + DBC *dbc= (DBC *) hdbc; + DESC *desc= desc_alloc(NULL, SQL_DESC_ALLOC_USER, DESC_APP, DESC_UNKNOWN); + LIST *e; + + if (!desc) + return set_dbc_error(dbc, "HY001", "Memory allocation error", MYERR_S1001); + + desc->exp.dbc= dbc; + + /* add to this connection's list of explicit descriptors */ + e= (LIST *) my_malloc(sizeof(LIST), MYF(0)); + e->data= desc; + dbc->exp_desc= list_add(dbc->exp_desc, e); + + *pdesc= desc; + return SQL_SUCCESS; +} + + +/* + Free an explicitly allocated descriptor. This resets all statements + that it was associated with to their implicitly allocated descriptors. +*/ +SQLRETURN my_SQLFreeDesc(SQLHANDLE hdesc) +{ + DESC *desc= (DESC *) hdesc; + DBC *dbc= desc->exp.dbc; + LIST *lstmt; + LIST *ldesc; + LIST *next; + + if (!desc) + return SQL_ERROR; + if (desc->alloc_type != SQL_DESC_ALLOC_USER) + return set_desc_error(desc, "HY017", "Invalid use of an automatically " + "allocated descriptor handle.", MYERR_S1017); + + /* remove from DBC */ + for (ldesc= dbc->exp_desc; ldesc; ldesc= ldesc->next) + { + if (ldesc->data == desc) + { + dbc->exp_desc= list_delete(dbc->exp_desc, ldesc); + x_free(ldesc); + break; + } + } + + /* reset all stmts it was on - to their implicit desc */ + for (lstmt= desc->exp.stmts; lstmt; lstmt= next) + { + STMT *stmt= lstmt->data; + next= lstmt->next; + if (IS_APD(desc)) + stmt->apd= stmt->imp_apd; + else if (IS_ARD(desc)) + stmt->ard= stmt->imp_ard; + x_free(lstmt); + } + + desc_free(desc); + return SQL_SUCCESS; +} + + +/* @type : ODBC 3.0 API @purpose : allocates an environment, connection, statement, or descriptor handle @@ -523,6 +598,10 @@ error= my_SQLAllocStmt(InputHandle,OutputHandlePtr); break; + case SQL_HANDLE_DESC: + error= my_SQLAllocDesc(InputHandle, OutputHandlePtr); + break; + default: return set_conn_error(InputHandle,MYERR_S1C00,NULL,0); } @@ -555,6 +634,12 @@ case SQL_HANDLE_STMT: error= my_SQLFreeStmt((STMT *)Handle, SQL_DROP); break; + + case SQL_HANDLE_DESC: + error= my_SQLFreeDesc((DESC *) Handle); + break; + + default: break; } @@ -562,3 +647,4 @@ return error; } + Index: driver/options.c =================================================================== --- driver/options.c (revision 977) +++ driver/options.c (working copy) @@ -355,7 +355,7 @@ /* 3.x driver doesn't support any statement attributes at connection level, but to make sure all 2.x apps - works fine...lets support it..nothing to loose.. + works fine...lets support it..nothing to lose.. */ default: return set_constmt_attr(2, dbc, &dbc->stmt_options, Attribute, @@ -518,6 +518,76 @@ options->cursor_type= SQL_CURSOR_STATIC; break; + case SQL_ATTR_APP_PARAM_DESC: + case SQL_ATTR_APP_ROW_DESC: + { + DESC *desc= (DESC *) ValuePtr; + DESC **dest= NULL; + desc_desc_type desc_type; + + /* reset to implicit if null */ + if (desc == SQL_NULL_HANDLE) + { + if (Attribute == SQL_ATTR_APP_PARAM_DESC) + stmt->apd= stmt->imp_apd; + else if (Attribute == SQL_ATTR_APP_ROW_DESC) + stmt->ard= stmt->imp_ard; + break; + } + + if (desc->alloc_type == SQL_DESC_ALLOC_AUTO && + desc->stmt != stmt) + return set_error(hstmt,MYERR_S1017, + "Invalid use of an automatically allocated " + "descriptor handle",0); + + if (desc->alloc_type == SQL_DESC_ALLOC_USER && + stmt->dbc != desc->exp.dbc) + return set_error(hstmt,MYERR_S1024, + "Invalid attribute value",0); + + if (Attribute == SQL_ATTR_APP_PARAM_DESC) + { + dest= &stmt->apd; + desc_type= DESC_PARAM; + } + else if (Attribute == SQL_ATTR_APP_ROW_DESC) + { + dest= &stmt->ard; + desc_type= DESC_ROW; + } + + if (desc->desc_type != DESC_UNKNOWN && + desc->desc_type != desc_type) + { + return set_error(hstmt,MYERR_S1024, + "Descriptor type mismatch",0); + } + + assert(desc); + assert(dest); + + if (desc->alloc_type == SQL_DESC_ALLOC_AUTO && + (*dest)->alloc_type == SQL_DESC_ALLOC_USER) + /* + If we're setting back the original implicit + descriptor, we must disassociate this statement + from the explicit descriptor. + */ + desc_remove_stmt(*dest, stmt); + else if (desc->alloc_type == SQL_DESC_ALLOC_USER) + { + /* otherwise, associate this statement with the desc */ + LIST *e= (LIST *) my_malloc(sizeof(LIST), MYF(0)); + e->data= stmt; + desc->exp.stmts= list_add(desc->exp.stmts, e); + } + + desc->desc_type= desc_type; + *dest= desc; + } + break; + case SQL_ATTR_AUTO_IPD: case SQL_ATTR_ENABLE_AUTO_IPD: if (ValuePtr != (SQLPOINTER)SQL_FALSE) @@ -525,6 +595,11 @@ "Optional feature not implemented",0); break; + case SQL_ATTR_IMP_PARAM_DESC: + case SQL_ATTR_IMP_ROW_DESC: + return set_error(hstmt,MYERR_S1024, + "Invalid attribute/option identifier",0); + case SQL_ATTR_PARAM_BIND_OFFSET_PTR: return stmt_SQLSetDescField(stmt, stmt->apd, 0, SQL_DESC_BIND_OFFSET_PTR, @@ -551,9 +626,13 @@ ValuePtr, SQL_IS_POINTER); case SQL_ATTR_PARAMSET_SIZE: + return set_error(hstmt,MYERR_01S02, + "Param arrays not supported",0); +#if ALLOW_SETTING_DESC_ARRAY_SIZE return stmt_SQLSetDescField(stmt, stmt->apd, 0, SQL_DESC_ARRAY_SIZE, ValuePtr, SQL_IS_ULEN); +#endif case SQL_ATTR_ROW_ARRAY_SIZE: case SQL_ROWSET_SIZE: @@ -594,16 +673,10 @@ options->simulateCursor= (SQLUINTEGER)(SQLULEN)ValuePtr; break; - /* TODO add setting descriptor support */ - /* only explicitely allocated descriptors can be set */ - /* HY017 - * Invalid use of an automatically allocated descriptor handle - */ - /* 3.x driver doesn't support any statement attributes at connection level, but to make sure all 2.x apps - works fine...lets support it..nothing to loose.. + works fine...lets support it..nothing to lose.. */ default: result= set_constmt_attr(3,hstmt,options, @@ -728,7 +801,7 @@ /* 3.x driver doesn't support any statement attributes at connection level, but to make sure all 2.x apps - works fine...lets support it..nothing to loose.. + works fine...lets support it..nothing to lose.. */ default: result= get_constmt_attr(3,hstmt,options, Index: CMakeLists.txt =================================================================== --- CMakeLists.txt (revision 977) +++ CMakeLists.txt (working copy) @@ -116,6 +116,11 @@ ADD_DEFINITIONS(-DENGLISH -DMYODBC_EXPORTS -D_USERDLL) ADD_DEFINITIONS(-D_WIN32 -DWIN32 -D_WINDOWS -D__WIN__) + # workaround for bug#33286 + IF(CMAKE_BUILD_TYPE STREQUAL "Debug") + ADD_DEFINITIONS(-DSAFEMALLOC) + ENDIF(CMAKE_BUILD_TYPE STREQUAL "Debug") + # edits for all config build flags FOREACH(TYPE C CXX) # makefiles use blank configuration @@ -169,3 +174,4 @@ ADD_SUBDIRECTORY(test) + Index: test/my_desc.c =================================================================== --- test/my_desc.c (revision 977) +++ test/my_desc.c (working copy) @@ -253,13 +253,286 @@ } +/* + Basic use of explicitly allocated descriptor +*/ +DECLARE_TEST(t_basic_explicit) +{ + SQLHANDLE expapd; + SQLINTEGER result; + SQLINTEGER impparam= 2; + SQLINTEGER expparam= 999; + + ok_stmt(hstmt, SQLPrepare(hstmt, (SQLCHAR *) "select ?", SQL_NTS)); + ok_stmt(hstmt, SQLBindCol(hstmt, 1, SQL_C_LONG, &result, 0, NULL)); + ok_stmt(hstmt, SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, + SQL_INTEGER, 0, 0, &impparam, 0, NULL)); + + ok_stmt(hstmt, SQLExecute(hstmt)); + + result= 0; + ok_stmt(hstmt, SQLFetch(hstmt)); + is_num(result, impparam); + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + /* setup a new descriptor */ + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &expapd)); + + ok_stmt(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_APP_PARAM_DESC, expapd, 0)); + ok_stmt(hstmt, SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, + SQL_INTEGER, 0, 0, &expparam, 0, NULL)); + + ok_stmt(hstmt, SQLExecute(hstmt)); + + result= 0; + ok_stmt(hstmt, SQLFetch(hstmt)); + is_num(result, expparam); + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + /* free the descriptor, will set apd back to original on hstmt */ + ok_desc(expapd, SQLFreeHandle(SQL_HANDLE_DESC, expapd)); + + ok_stmt(hstmt, SQLExecute(hstmt)); + + result= 0; + ok_stmt(hstmt, SQLFetch(hstmt)); + is_num(result, impparam); + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + return OK; +} + + +/* + Test the various error scenarios possible with + explicitly allocated descriptors +*/ +DECLARE_TEST(t_explicit_error) +{ + SQLHANDLE desc1, desc2; + SQLHANDLE expapd; + SQLHANDLE hdbc2; + SQLHANDLE hstmt2; + + /* TODO using an exp from a different dbc */ + + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt2)); + ok_stmt(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, + &desc1, 0, NULL)); + ok_stmt(hstmt2, SQLGetStmtAttr(hstmt2, SQL_ATTR_APP_ROW_DESC, + &desc2, 0, NULL)); + + /* can't set implicit ard from a different statement */ + expect_stmt(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, + desc2, 0), SQL_ERROR); + is(check_sqlstate(hstmt, "HY017") == OK); + + /* can set it to the same statement */ + ok_stmt(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, desc1, 0)); + + /* can't set implementation descriptors */ + expect_stmt(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_IMP_ROW_DESC, + desc1, 0), SQL_ERROR); + is(check_sqlstate(hstmt, "HY024") == OK || + check_sqlstate(hstmt, "HY017") == OK); + + /* can't free implicit descriptors */ + expect_desc(desc1, SQLFreeHandle(SQL_HANDLE_DESC, desc1), SQL_ERROR); + is(check_sqlstate_ex(desc1, SQL_HANDLE_DESC, "HY017") == OK); + + /* can't set apd as ard (and vice-versa) */ + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &expapd)); + ok_stmt(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_APP_PARAM_DESC, + expapd, 0)); /* this makes expapd an apd */ + + expect_stmt(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, + expapd, 0), SQL_ERROR); + is(check_sqlstate(hstmt, "HY024") == OK); + + /* + this exposes a bug in unixODBC (2.2.12 and current as of 2007-12-14). + Even though the above call failed, unixODBC saved this value internally + and returns it. desc1 should *not* be the same as the explicit apd + */ + ok_stmt(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, + &desc1, 0, NULL)); + printMessage("explicit apd: %x, stmt's ard: %x", expapd, desc1); + + return OK; +} + + +/* + Test that statements are handled correctly when freeing an + explicit descriptor associated with multiple statements. +*/ +DECLARE_TEST(t_mult_stmt_free) +{ +#define mult_count 3 + SQLHANDLE expard, expapd, desc; + SQLHANDLE stmt[mult_count]; + SQLINTEGER i; + SQLINTEGER imp_params[mult_count]; + SQLINTEGER imp_results[mult_count]; + SQLINTEGER exp_param; + SQLINTEGER exp_result; + + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &expapd)); + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &expard)); + + for (i= 0; i < mult_count; ++i) + { + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &stmt[i])); + /* we bind these now, but use at the end */ + imp_params[i]= 900 + i; + ok_stmt(stmt[i], SQLBindParameter(stmt[i], 1, SQL_PARAM_INPUT, SQL_INTEGER, + SQL_C_LONG, 0, 0, &imp_params[i], 0, NULL)); + ok_stmt(stmt[i], SQLBindCol(stmt[i], 1, SQL_C_LONG, &imp_results[i], 0, NULL)); + ok_stmt(stmt[i], SQLSetStmtAttr(stmt[i], SQL_ATTR_APP_ROW_DESC, expard, 0)); + ok_stmt(stmt[i], SQLSetStmtAttr(stmt[i], SQL_ATTR_APP_PARAM_DESC, expapd, 0)); + } + + /* this will work for all */ + ok_stmt(stmt[0], SQLBindParameter(stmt[0], 1, SQL_PARAM_INPUT, SQL_INTEGER, + SQL_C_LONG, 0, 0, &exp_param, 0, NULL)); + ok_stmt(stmt[0], SQLBindCol(stmt[0], 1, SQL_C_LONG, &exp_result, 0, NULL)); + + /* check that the explicit ard and apd are working */ + for (i= 0; i < mult_count; ++i) + { + exp_param= 200 + i; + ok_stmt(stmt[i], SQLExecDirect(stmt[i], "select ?", SQL_NTS)); + ok_stmt(stmt[i], SQLFetch(stmt[i])); + is_num(exp_result, exp_param); + ok_stmt(stmt[i], SQLFreeStmt(stmt[i], SQL_CLOSE)); + } + + /* + Windows ODBC DM has a bug that crashes when using a statement + after free-ing an explicitly allocated that was associated with + it. (This exact test was run against SQL Server and crashed in + the exact same spot.) Tested on Windows 2003 x86 and + Windows XP x64 both w/MDAC 2.8. + */ +#ifndef _WIN32 + /* + now free the explicit apd+ard and the stmts should go back to + their implicit descriptors + */ + ok_desc(expard, SQLFreeHandle(SQL_HANDLE_DESC, expard)); + ok_desc(expapd, SQLFreeHandle(SQL_HANDLE_DESC, expapd)); + + /* check that the original values worked */ + for (i= 0; i < mult_count; ++i) + { + ok_stmt(stmt[i], SQLExecDirect(stmt[i], "select ?", SQL_NTS)); + ok_stmt(stmt[i], SQLFetch(stmt[i])); + ok_stmt(stmt[i], SQLFreeStmt(stmt[i], SQL_CLOSE)); + } + + for (i= 0; i < mult_count; ++i) + { + is_num(imp_results[i], imp_params[i]); + } + + /* + bug in unixODBC - it still returns the explicit descriptor + These should *not* be the same + */ + ok_stmt(hstmt, SQLGetStmtAttr(stmt[0], SQL_ATTR_APP_ROW_DESC, + &desc, SQL_IS_POINTER, NULL)); + printMessage("explicit ard = %x, stmt[0]'s implicit ard = %x", expard, desc); +#endif + + return OK; +#undef mult_count +} + + +/* + Test that when we set a stmt's ard from an explicit descriptor to + null, it uses the implicit descriptor again. Also the statement + will disassociate itself from the explicit descriptor. +*/ +DECLARE_TEST(t_set_null_use_implicit) +{ + SQLHANDLE expard, hstmt1; + SQLINTEGER imp_result= 0, exp_result= 0; + + /* + we use a separate statement handle to test that it correctly + disassociates itself from the descriptor + */ + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt1)); + + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &expard)); + + /* this affects the implicit ard */ + ok_stmt(hstmt1, SQLBindCol(hstmt1, 1, SQL_C_LONG, &imp_result, 0, NULL)); + + /* set the explicit ard */ + ok_stmt(hstmt1, SQLSetStmtAttr(hstmt1, SQL_ATTR_APP_ROW_DESC, expard, 0)); + + /* this affects the expard */ + ok_stmt(hstmt1, SQLBindCol(hstmt1, 1, SQL_C_LONG, &exp_result, 0, NULL)); + + /* set it to null, getting rid of the expard */ + ok_stmt(hstmt1, SQLSetStmtAttr(hstmt1, SQL_ATTR_APP_ROW_DESC, + SQL_NULL_HANDLE, 0)); + + ok_sql(hstmt1, "select 1"); + ok_stmt(hstmt1, SQLFetch(hstmt1)); + + is_num(exp_result, 0); + is_num(imp_result, 1); + + ok_stmt(hstmt1, SQLFreeHandle(SQL_HANDLE_STMT, hstmt1)); + + /* if stmt disassociation failed, this will crash */ + ok_desc(expard, SQLFreeHandle(SQL_HANDLE_DESC, expard)); + + return OK; +} + + +/* + Test free-ing a statement that has an explicitely allocated + descriptor associated with it. If this test fails, it will + crash due to the statement not being disassociated with the + descriptor correctly. +*/ +DECLARE_TEST(t_free_stmt_with_exp_desc) +{ + SQLHANDLE expard, hstmt1; + SQLINTEGER imp_result= 0, exp_result= 0; + + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &expard)); + ok_con(hdbc, SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt1)); + + /* set the explicit ard */ + ok_stmt(hstmt1, SQLSetStmtAttr(hstmt1, SQL_ATTR_APP_ROW_DESC, expard, 0)); + + /* free the statement, THEN the descriptors */ + ok_stmt(hstmt1, SQLFreeHandle(SQL_HANDLE_STMT, hstmt1)); + ok_desc(expard, SQLFreeHandle(SQL_HANDLE_DESC, expard)); + + return OK; +} + + BEGIN_TESTS - ADD_TEST(t_desc_paramset) + ADD_TODO(t_desc_paramset) ADD_TEST(t_desc_set_error) ADD_TEST(t_sqlbindcol_count_reset) ADD_TEST(t_desc_default_type) + ADD_TEST(t_basic_explicit) + ADD_TEST(t_explicit_error) + ADD_TEST(t_mult_stmt_free) + ADD_TEST(t_set_null_use_implicit) + ADD_TEST(t_free_stmt_with_exp_desc) END_TESTS RUN_TESTS