=== modified file 'driver/driver.h' --- driver/driver.h 2008-10-09 17:29:02 +0000 +++ driver/driver.h 2008-12-21 23:03:44 +0000 @@ -386,7 +386,8 @@ /* Statement states */ -enum MY_STATE { ST_UNKNOWN, ST_PREPARED, ST_PRE_EXECUTED, ST_EXECUTED }; +enum MY_STATE { ST_UNKNOWN, ST_PREPARED, ST_PRE_EXECUTED, ST_EXECUTED, + ST_SS_PREPARED }; enum MY_DUMMY_STATE { ST_DUMMY_UNKNOWN, ST_DUMMY_PREPARED, ST_DUMMY_EXECUTED }; @@ -416,6 +417,7 @@ { DBC FAR *dbc; MYSQL_RES *result; + MYSQL_STMT *prepstmt; my_bool fake_result; MYSQL_ROW array,result_array,current_values; MYSQL_ROW (*fix_fields)(struct tagSTMT FAR* stmt,MYSQL_ROW row); === modified file 'driver/error.c' --- driver/error.c 2008-08-22 22:12:25 +0000 +++ driver/error.c 2008-12-21 23:35:25 +0000 @@ -47,7 +47,7 @@ {"01S04","More than one row updated/deleted", SQL_SUCCESS_WITH_INFO}, {"01S06","Attempt to fetch before the result set returned the first rowset", SQL_SUCCESS_WITH_INFO}, - {"07001","SQLBindParameter not used for all parameters", SQL_ERROR}, + {"07002","SQLBindParameter not used for all parameters", SQL_ERROR}, {"07005","Prepared statement not a cursor-specification", SQL_ERROR}, {"07006","Restricted data type attribute violation", SQL_ERROR}, {"07009","Invalid descriptor index", SQL_ERROR}, === modified file 'driver/error.h' --- driver/error.h 2008-08-22 22:12:25 +0000 +++ driver/error.h 2008-12-21 23:35:49 +0000 @@ -88,7 +88,7 @@ MYERR_01S03, MYERR_01S04, MYERR_01S06, - MYERR_07001, + MYERR_07002, MYERR_07005, MYERR_07006, MYERR_07009, === modified file 'driver/execute.c' --- driver/execute.c 2008-09-03 16:07:44 +0000 +++ driver/execute.c 2008-12-21 23:36:32 +0000 @@ -70,6 +70,16 @@ goto exit; } + /* free prep stmt if we used it for metadata + this has to be done before executing the query */ + if (stmt->prepstmt) + { + mysql_stmt_close(stmt->prepstmt); + stmt->prepstmt= NULL; + /* we can't use the ird data after freeing the stmt */ + stmt->ird->count= 0; + } + if ( mysql_query(&stmt->dbc->mysql,query) ) { set_stmt_error(stmt,"HY000",mysql_error(&stmt->dbc->mysql), @@ -99,7 +109,9 @@ mysql_errno(&stmt->dbc->mysql)); goto exit; } - fix_result_types(stmt); + + stmt->state= ST_EXECUTED; + fix_result_types(stmt, stmt->result); error= SQL_SUCCESS; exit: @@ -191,7 +203,7 @@ if (stmt->dummy_state != ST_DUMMY_PREPARED && !aprec->par.real_param_done) { - rc= set_error(stmt,MYERR_07001,NULL,0); + rc= set_error(stmt,MYERR_07002,NULL,0); goto error; } get_dynamic(&stmt->param_pos, (void *)&pos, i); === modified file 'driver/handle.c' --- driver/handle.c 2008-09-03 05:37:01 +0000 +++ driver/handle.c 2008-12-21 23:30:41 +0000 @@ -456,6 +456,12 @@ return SQL_SUCCESS; stmt->state= ST_UNKNOWN; + stmt->ird->count= 0; + if (stmt->prepstmt) + { + mysql_stmt_close(stmt->prepstmt); + stmt->prepstmt= NULL; + } x_free(stmt->table_name); stmt->table_name= 0; === modified file 'driver/myutil.h' --- driver/myutil.h 2008-12-05 19:07:12 +0000 +++ driver/myutil.h 2008-12-21 15:37:22 +0000 @@ -123,8 +123,9 @@ SQLRETURN do_query(STMT FAR *stmt,char *query); SQLRETURN insert_params(STMT FAR *stmt, char **finalquery); SQLRETURN odbc_stmt(DBC FAR *dbc, const char *query); +BOOL isStatementForRead(STMT *stmt); void mysql_link_fields(STMT *stmt,MYSQL_FIELD *fields,uint field_count); -void fix_result_types(STMT *stmt); +void fix_result_types(STMT *stmt, MYSQL_RES *result); char *fix_str(char *to,const char *from,int length); char *dupp_str(char *from,int length); SQLRETURN my_pos_delete(STMT FAR *stmt,STMT FAR *stmtParam, === modified file 'driver/prepare.c' --- driver/prepare.c 2008-08-22 22:12:25 +0000 +++ driver/prepare.c 2008-12-21 23:09:09 +0000 @@ -85,6 +85,7 @@ uint param_count; CHARSET_INFO *charset_info= stmt->dbc->mysql.charset; int bPerhapsEmbraced= 1, bEmbraced= 0; + MYSQL_RES *cols; LINT_INIT(end); @@ -99,6 +100,8 @@ if (!(stmt->query= dupp_str((char *)szSqlStr, cbSqlStr))) return set_error(stmt, MYERR_S1001, NULL, 4001); + stmt->ird->count= 0; + /* Count number of parameters and save position for each parameter */ in_string= 0; param_count= 0; @@ -176,11 +179,59 @@ /* remove closing brace if we have one */ if (pcLastCloseBrace) *pcLastCloseBrace= ' '; - + + /* prepare statement to retrieve column info */ + if (isStatementForRead(stmt)) + { + char state[6]; + const char *sstate; + unsigned int errnum; + if (!(stmt->prepstmt= mysql_stmt_init(&stmt->dbc->mysql))) + return set_error(stmt, MYERR_S1001, NULL, 4001); + if (mysql_stmt_prepare(stmt->prepstmt, stmt->query, strlen(stmt->query))) + { + /* copy prep stmt error if failed */ + errnum= mysql_stmt_errno(stmt->prepstmt); + switch (errnum) + { + case /*CR_SERVER_GONE_ERROR*/2006: + case /*CR_SERVER_LOST*/2013: + sprintf(state, "08S01"); + break; + default: + sstate= mysql_stmt_sqlstate(stmt->prepstmt); + if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC2 && + !strncmp(sstate, "42S", 3)) + sprintf(state, "S00%s", sstate + 3); + else + memcpy(state, sstate, 6); + } + + set_stmt_error(stmt, state, (const char *) mysql_stmt_error(stmt->prepstmt), + errnum); + mysql_stmt_close(stmt->prepstmt); + stmt->prepstmt= NULL; + return SQL_ERROR; + } + + if (cols= mysql_stmt_result_metadata(stmt->prepstmt)) + { + stmt->state= ST_SS_PREPARED; + fix_result_types(stmt, cols); + } + /* if theres no result metadata, it will be filled in at execute */ + else + { + mysql_stmt_close(stmt->prepstmt); + stmt->prepstmt= NULL; + } + } + /* Reset current_param so that SQLParamData starts fresh. */ stmt->current_param= 0; stmt->query_end= pos; - stmt->state= ST_PREPARED; + if (stmt->state != ST_SS_PREPARED) + stmt->state= ST_PREPARED; stmt->param_count= param_count; return SQL_SUCCESS; === modified file 'driver/results.c' --- driver/results.c 2008-12-05 19:07:12 +0000 +++ driver/results.c 2008-12-21 22:38:31 +0000 @@ -454,8 +454,9 @@ szToken[n] = '\0'; - if ( strcmp( szToken, "SELECT" ) == 0 || strcmp( szToken, "SHOW" ) == 0 ) - return TRUE; + if (!strcmp(szToken, "SELECT") || !strcmp(szToken, "SHOW") || + !strcmp(szToken, "CALL")) + return TRUE; return FALSE; } @@ -485,6 +486,7 @@ else error = SQL_SUCCESS; break; + case ST_SS_PREPARED: case ST_PRE_EXECUTED: case ST_EXECUTED: error= SQL_SUCCESS; @@ -534,19 +536,10 @@ SQLRETURN error; STMT FAR *stmt= (STMT FAR*) hstmt; - if ( stmt->param_count > 0 && stmt->dummy_state == ST_DUMMY_UNKNOWN && - (stmt->state != ST_PRE_EXECUTED || stmt->state != ST_EXECUTED) ) - { - if ( do_dummy_parambind(hstmt) != SQL_SUCCESS ) - return SQL_ERROR; - } if ( (error= check_result(stmt)) != SQL_SUCCESS ) return error; - if ( !stmt->result ) - *pccol= 0; /* Not a select */ - else - *pccol= stmt->result->field_count; + *pccol= (SQLSMALLINT) stmt->ird->count; return SQL_SUCCESS; } @@ -575,8 +568,6 @@ if ((error= check_result(stmt)) != SQL_SUCCESS) return error; - if (!stmt->result) - return set_stmt_error(stmt, "07005", "No result set", 0); if (column == 0 || column > stmt->ird->count) return set_stmt_error(stmt, "07009", "Invalid descriptor index", 0); @@ -643,9 +634,6 @@ if (check_result(stmt) != SQL_SUCCESS) return SQL_ERROR; - if (!stmt->result) - return set_stmt_error(stmt, "07005", "No result set", 0); - /* we report bookmark type if requested, nothing else */ if (attrib == SQL_DESC_TYPE && column == 0) { @@ -659,9 +647,6 @@ if (!num_attr) num_attr= &nparam; - if ((error= check_result(stmt)) != SQL_SUCCESS) - return error; - if (attrib == SQL_DESC_COUNT || attrib == SQL_COLUMN_COUNT) { *num_attr= stmt->ird->count; @@ -1037,7 +1022,8 @@ nReturn = set_stmt_error( pStmt, "HY000", mysql_error( &pStmt->dbc->mysql ), mysql_errno( &pStmt->dbc->mysql ) ); goto exitSQLMoreResults; } - fix_result_types( pStmt ); + pStmt->state= ST_EXECUTED; + fix_result_types(pStmt, pStmt->result); exitSQLMoreResults: pthread_mutex_unlock( &pStmt->dbc->lock ); === modified file 'driver/utility.c' --- driver/utility.c 2008-11-14 17:34:50 +0000 +++ driver/utility.c 2008-12-21 22:56:43 +0000 @@ -69,7 +69,8 @@ result->fields= fields; result->field_count= field_count; result->current_field= 0; - fix_result_types(stmt); + stmt->state= ST_EXECUTED; + fix_result_types(stmt, stmt->result); pthread_mutex_unlock(&stmt->dbc->lock); } @@ -79,16 +80,13 @@ @param[in] stmt The statement with result types to be fixed. */ -void fix_result_types(STMT *stmt) +void fix_result_types(STMT *stmt, MYSQL_RES *result) { uint i; - MYSQL_RES *result= stmt->result; DESCREC *irrec; MYSQL_FIELD *field; int capint32= stmt->dbc->flag & FLAG_COLUMN_SIZE_S32 ? 1 : 0; - stmt->state= ST_EXECUTED; /* Mark set found */ - /* Populate the IRD records */ for (i= 0; i < result->field_count; ++i) { === modified file 'test/my_basics.c' --- test/my_basics.c 2008-09-11 03:02:39 +0000 +++ test/my_basics.c 2008-12-21 23:40:56 +0000 @@ -443,6 +443,7 @@ "INSERT INTO t_bug7445 VALUES ('georg');" "INSERT INTO t_bug7445 VALUES ('tonci');" "INSERT INTO t_bug7445 VALUES ('jim')"); + ok_stmt(hstmt1, SQLFreeStmt(hstmt1, SQL_CLOSE)); ok_sql(hstmt1, "SELECT COUNT(*) FROM t_bug7445"); === modified file 'test/my_error.c' --- test/my_error.c 2008-08-22 22:12:25 +0000 +++ test/my_error.c 2008-12-21 23:36:22 +0000 @@ -326,7 +326,7 @@ ok_stmt(hstmt, SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &i, 0, NULL)); expect_stmt(hstmt, SQLExecute(hstmt), SQL_ERROR); - return check_sqlstate(hstmt, "07001"); + return check_sqlstate(hstmt, "07002"); } @@ -348,7 +348,7 @@ ok_stmt(hstmt, SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &i, 0, NULL)); expect_stmt(hstmt, SQLExecute(hstmt), SQL_ERROR); - return check_sqlstate(hstmt, "07001"); + return check_sqlstate(hstmt, "07002"); } === modified file 'test/my_prepare.c' --- test/my_prepare.c 2008-08-22 22:12:25 +0000 +++ test/my_prepare.c 2008-12-21 23:36:09 +0000 @@ -1072,6 +1072,101 @@ } +/* + Retrieve the result meta-data from a prepared statement. +*/ +DECLARE_TEST(t_ssps_result_metadata) +{ + SQLCHAR name[10]; + SQLSMALLINT type, nullable; + SQLINTEGER size; + SQLSMALLINT cols; + ok_sql(hstmt, "DROP TABLE IF EXISTS t_ssps_resmd"); + ok_sql(hstmt, "CREATE TABLE t_ssps_resmd " + "(a INT NOT NULL, b TEXT, c123 VARCHAR(16))"); + + ok_stmt(hstmt, SQLPrepare(hstmt, "select a, b, c123, 1, ? as p " + "from t_ssps_resmd where a = ?", SQL_NTS)); + + ok_stmt(hstmt, SQLNumResultCols(hstmt, &cols)); + is_num(5, cols); + + ok_stmt(hstmt, SQLDescribeCol(hstmt, 1, name, 10, NULL, + &type, &size, NULL, &nullable)); + is_str("a", name, 2); + is_num(SQL_INTEGER, type); + is_num(10, size); + is_num(SQL_NO_NULLS, nullable); + + ok_stmt(hstmt, SQLDescribeCol(hstmt, 2, name, 10, NULL, + &type, &size, NULL, &nullable)); + is_str("b", name, 2); + is_num(SQL_LONGVARCHAR, type); + is_num(65535, size); + is_num(SQL_NULLABLE, nullable); + + ok_stmt(hstmt, SQLDescribeCol(hstmt, 3, name, 10, NULL, + &type, &size, NULL, &nullable)); + is_str("c123", name, 2); + is_num(SQL_VARCHAR, type); + is_num(16, size); + is_num(SQL_NULLABLE, nullable); + + ok_stmt(hstmt, SQLDescribeCol(hstmt, 4, name, 10, NULL, + &type, &size, NULL, &nullable)); + is_str("1", name, 2); + is_num(SQL_BIGINT, type); + is_num(19, size); + is_num(SQL_NO_NULLS, nullable); + + ok_stmt(hstmt, SQLDescribeCol(hstmt, 5, name, 10, NULL, + &type, &size, NULL, &nullable)); + is_str("p", name, 2); + is_num(SQL_VARBINARY, type); + is_num(0, size); + is_num(SQL_NULLABLE, nullable); + + ok_sql(hstmt, "DROP TABLE IF EXISTS t_ssps_resmd"); + return OK; +} + + +/* + Test other stuff related to server side prepare. +*/ +DECLARE_TEST(t_ssps_error) +{ + SQLSMALLINT cols; + ok_sql(hstmt, "DROP TABLE IF EXISTS t_ssps_other"); + ok_sql(hstmt, "CREATE TABLE t_ssps_other (a int)"); + ok_sql(hstmt, "DROP PROCEDURE IF EXISTS t_sp"); + ok_sql(hstmt,"create procedure t_sp(x int) " + "begin select x; end;"); + + /* will not server side prepare, should be 0 result cols */ + ok_stmt(hstmt, SQLPrepare(hstmt, "drop table t_ssps_other", SQL_NTS)); + ok_stmt(hstmt, SQLNumResultCols(hstmt, &cols)); + is_num(0, cols); + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + /* will ssps, but returns 0 result cols, so also pre-execute at SQLNumResultCols() */ + ok_stmt(hstmt, SQLPrepare(hstmt, "show create table t_ssps_other", SQL_NTS)); + ok_stmt(hstmt, SQLNumResultCols(hstmt, &cols)); + is_num(2, cols); + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + /* will not ssps, and pre-execute will fail */ + ok_stmt(hstmt, SQLPrepare(hstmt, "call t_sp(?)", SQL_NTS)); + expect_stmt(hstmt, SQLNumResultCols(hstmt, &cols), SQL_ERROR); + is(check_sqlstate(hstmt, "07002") == OK); + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + ok_sql(hstmt, "DROP TABLE IF EXISTS t_ssps_other"); + ok_sql(hstmt, "DROP PROCEDURE IF EXISTS t_sp"); + return OK; +} + + BEGIN_TESTS ADD_TEST(t_prep_basic) ADD_TEST(t_prep_buffer_length) @@ -1087,6 +1182,8 @@ ADD_TEST(tmysql_bindparam) ADD_TEST(t_acc_update) ADD_TEST(t_bug29871) + ADD_TEST(t_ssps_result_metadata) + ADD_TEST(t_ssps_error) END_TESTS