diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 773ad953..e72b5dc2 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -593,12 +593,26 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) if ( execReturn == SQL_NO_DATA ) { stmt->column_count = 0; stmt->row_count = 0; + driver_stmt->column_count = 0; + driver_stmt->row_count = 0; } else { - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) { + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + driver_stmt->column_count = stmt->column_count; + } + else { + stmt->column_count = driver_stmt->column_count; + } - // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) { + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + driver_stmt->row_count = stmt->row_count; + } + else { + stmt->row_count = driver_stmt->row_count; + } } // workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with @@ -692,11 +706,16 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); - // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the - // pdo_stmt_t::row_count member - if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + // support for the PDO rowCount method. Since rowCount doesn't call a + // method, PDO relies on us to fill the pdo_stmt_t::row_count member + // The if condition was changed from + // `driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY` + // because it caused SQLRowCount to be called at each fetch if using a non-forward cursor + // which is unnecessary and a performance hit + if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) { stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + driver_stmt->row_count = stmt->row_count; // a row_count of -1 means no rows, but we change it to 0 if( stmt->row_count == -1 ) { @@ -1146,6 +1165,9 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // return the row count regardless if there are any rows or not stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + driver_stmt->column_count = stmt->column_count; + driver_stmt->row_count = stmt->row_count; } catch( core::CoreException& ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index d4ffdda5..347a89d7 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -182,6 +182,11 @@ const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2; // max size of a date time string when converting from a DateTime object to a string const int MAX_DATETIME_STRING_LEN = 256; +// identifier for whether or not we have obtained the number of rows and columns +// of a result +const short ACTIVE_NUM_COLS_INVALID = -99; +const long ACTIVE_NUM_ROWS_INVALID = -99; + // precision and scale for the date time types between servers const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; @@ -1438,8 +1443,9 @@ struct sqlsrv_stmt : public sqlsrv_context { bool has_rows; // Has_rows is set if there are actual rows in the row set bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field - bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the - // last results + bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the last results + short column_count; // Number of columns in the current result set obtained from SQLNumResultCols + long row_count; // Number of rows in the current result set obtained from SQLRowCount unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 9cac96a5..19c448e5 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -140,6 +140,8 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error fetch_called( false ), last_field_index( -1 ), past_next_result_end( false ), + column_count( ACTIVE_NUM_COLS_INVALID ), + row_count( ACTIVE_NUM_ROWS_INVALID ), query_timeout( QUERY_TIMEOUT_INVALID ), date_as_string(false), format_decimals(false), // no formatting needed @@ -225,6 +227,8 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) this->past_next_result_end = false; this->past_fetch_end = false; this->last_field_index = -1; + this->column_count = ACTIVE_NUM_COLS_INVALID; + this->row_count = ACTIVE_NUM_ROWS_INVALID; // delete any current results if( current_results ) { @@ -819,9 +823,17 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { throw core::CoreException(); } + // First time only if ( !stmt->fetch_called ) { - SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + SQLSMALLINT has_fields; + if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) { + has_fields = stmt->column_count; + } else { + has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + stmt->column_count = has_fields; + } + CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { throw core::CoreException(); } @@ -1066,10 +1078,27 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { - // Use SQLNumResultCols to determine if we have rows or not. - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - // use SQLRowCount to determine if there is a rows status waiting - SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + SQLSMALLINT num_cols; + SQLLEN rows_affected; + + if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) { + num_cols = stmt->column_count; + } + else { + // Use SQLNumResultCols to determine if we have rows or not + num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + stmt->column_count = num_cols; + } + + if (stmt->row_count != ACTIVE_NUM_ROWS_INVALID) { + rows_affected = stmt->row_count; + } + else { + // Use SQLRowCount to determine if there is a rows status waiting + rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + stmt->row_count = rows_affected; + } + return (num_cols != 0) || (rows_affected > 0); } diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 9d60de25..9d16d24e 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1791,7 +1791,12 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) if (num_cols == 0) { getMetaData = true; - num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) { + num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + stmt->column_count = num_cols; + } else { + num_cols = stmt->column_count; + } } try { diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index cec66c3f..e4ea3472 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -205,8 +205,8 @@ ss_error SS_ERRORS[] = { }, { - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } }, { diff --git a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt index 7f751324..92b7f0ae 100644 --- a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt @@ -153,7 +153,7 @@ echo "Null result set, call next result first: #############################\n"; $stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); NextResult($stmt, []); -Fetch($stmt, [$errorFuncSeq()]); +Fetch($stmt, [$errorNoFields]); // Call next_result twice in succession on a null result set echo "Null result set, call next result twice: #############################\n";