From 2eab856b44ab4df9d4ea9f9447803d3d4f95a537 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 4 May 2020 17:06:31 -0700 Subject: [PATCH 01/12] PDO errorinfo includes additional odbc messages if available --- source/pdo_sqlsrv/pdo_util.cpp | 205 +++++++-------- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 1 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 + source/shared/core_sqlsrv.h | 34 ++- .../pdo_924_display_more_errors.phpt | 105 ++++++++ .../pdo_sqlsrv/pdo_924_log_all_warnings.phpt | 239 ++++++++++++++++++ .../pdo_sqlsrv/pdo_connection_logs.phpt | 6 + .../pdo_sqlsrv/pdo_errorMode_logs.phpt | 6 + .../pdo_mars_disabled_error_checks.phpt | 43 ++++ 9 files changed, 522 insertions(+), 119 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 58f49efa1..fc6af69b4 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -45,6 +45,9 @@ sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code); // build the object and throw the PDO exception void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ); +void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args); + +void add_extra_errors_to_array(_In_ sqlsrv_error const* error, _Inout_ zval* array_z); } // pdo driver error messages @@ -462,47 +465,27 @@ pdo_error PDO_ERRORS[] = { { UINT_MAX, {} } }; -// PDO error handler for the environment context. bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ) { - SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); - pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); - - sqlsrv_error_auto_ptr error; + SQLSRV_ASSERT((ctx != NULL), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null"); + pdo_dbh_t* dbh = reinterpret_cast(ctx.driver()); + SQLSRV_ASSERT((dbh != NULL), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null"); - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + sqlsrv_error_auto_ptr error; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args); - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); - } - else { + //strcpy_s(dbh->error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + if (!warning && dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + pdo_sqlsrv_throw_exception(error); } - strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); - - switch( dbh->error_mode ) { - - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error ); - } - ctx.set_last_error( error ); - break; - - default: - DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); - break; - } - + ctx.set_last_error(error); + // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count // return error ignored = true for warnings. - return ( warning ? true : false ); - + return (warning ? true : false); } // pdo error handler for the dbh context. @@ -513,95 +496,54 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT(strnlen_s(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); - strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); - - switch( dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - if( !warning ) { - size_t msg_len = strnlen_s( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE - + MAX_DIGITS + WARNING_MIN_LENGTH + 1; - sqlsrv_malloc_auto_ptr msg; - msg = static_cast( sqlsrv_malloc( msg_len ) ); - core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, - error->native_message ); - php_error(E_WARNING, "%s", msg.get()); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", dbh->error_mode ); - break; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args); + + //SQLSRV_ASSERT(strnlen_s(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); + //strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); + + if (!warning) { + if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + pdo_sqlsrv_throw_exception(error); + } + else if (dbh->error_mode == PDO_ERRMODE_WARNING) { + size_t msg_len = strnlen_s(reinterpret_cast(error->native_message)) + SQL_SQLSTATE_BUFSIZE + + MAX_DIGITS + WARNING_MIN_LENGTH + 1; + sqlsrv_malloc_auto_ptr msg; + msg = static_cast(sqlsrv_malloc(msg_len)); + core_sqlsrv_format_message(msg, static_cast(msg_len), WARNING_TEMPLATE, error->sqlstate, error->native_code, + error->native_message); + php_error(E_WARNING, "%s", msg.get()); + } } + ctx.set_last_error(error); + // return error ignored = true for warnings. - return ( warning ? true : false ); + return (warning ? true : false); } // PDO error handler for the statement context. -bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, - _In_opt_ va_list* print_args ) +bool pdo_sqlsrv_handle_stmt_error(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, + _In_opt_ va_list* print_args) { - pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); + pdo_stmt_t* pdo_stmt = reinterpret_cast(ctx.driver()); + SQLSRV_ASSERT(pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed"); sqlsrv_error_auto_ptr error; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, pdo_stmt->error_code, print_args); - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } + //SQLSRV_ASSERT(strnlen_s(reinterpret_cast(error->sqlstate)) <= sizeof(pdo_stmt->error_code), "Error code overflow"); + //strcpy_s(pdo_stmt->error_code, sizeof(pdo_stmt->error_code), reinterpret_cast(error->sqlstate)); - SQLSRV_ASSERT( strnlen_s( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); - strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); - - switch( pdo_stmt->dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); - break; - } + if (!warning && pdo_stmt->dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + pdo_sqlsrv_throw_exception(error); + } + ctx.set_last_error(error); // return error ignored = true for warnings. - return ( warning ? true : false ); + return (warning ? true : false); } - // Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: // 0, native code // 1, native message @@ -613,6 +555,8 @@ void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Ou // SQLSTATE is already present in the zval. add_next_index_long( pdo_zval, last_error->native_code ); add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); + + add_extra_errors_to_array(last_error, pdo_zval); } } @@ -650,10 +594,10 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); sqlsrv_malloc_auto_ptr ex_msg; - size_t ex_msg_len = strnlen_s( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + size_t ex_msg_len = strnlen_s(reinterpret_cast(error->native_message)) + SQL_SQLSTATE_BUFSIZE + 12 + 1; // 12 = "SQLSTATE[]: " - ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); - snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); + ex_msg = reinterpret_cast(sqlsrv_malloc(ex_msg_len)); + snprintf(ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, ex_msg ); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, @@ -665,6 +609,9 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); add_next_index_long( &ex_error_info, error->native_code ); add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); + + add_extra_errors_to_array(reinterpret_cast(error), &ex_error_info); + //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) //and the refcount of the zend_array is incremented by 1 zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, @@ -677,4 +624,46 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) zend_throw_exception_object( &ex_obj ); } +void add_extra_errors_to_array(_In_ sqlsrv_error const* error, _Inout_ zval* array_z) +{ + if (error->next != NULL && PDO_SQLSRV_G(report_additional_errors)) { + sqlsrv_error *p = error->next; + while (p != NULL) { + add_next_index_string(array_z, reinterpret_cast(p->sqlstate)); + add_next_index_long(array_z, p->native_code); + add_next_index_string(array_z, reinterpret_cast(p->native_message)); + + p = p-> next; + } + } +} + +void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args) +{ + if (sqlsrv_error_code != SQLSRV_ERROR_ODBC) { + core_sqlsrv_format_driver_error(ctx, get_error_message(sqlsrv_error_code), error, SEV_ERROR, print_args); + strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); + } + else { + bool result = core_sqlsrv_get_odbc_error(ctx, 1, error, SEV_ERROR); + if (result) { + // Check if there exist more errors + int rec_number = 2; + sqlsrv_error_auto_ptr err; + sqlsrv_error *p = error; + + do { + result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR); + if (result) { + p->next = err.get(); + err.transferred(); + p = p->next; + } + } while (result); + } + + strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); + } +} + } diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 79a294f95..d7f5ac0cb 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -31,6 +31,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) unsigned int pdo_log_severity; zend_long client_buffer_max_size; +short report_additional_errors; #ifndef _WIN32 zend_long set_locale_info; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index abf711ceb..e81cf09de 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -53,6 +53,7 @@ extern HMODULE g_sqlsrv_hmodule; // (these are defined as macros to allow concatenation as we do below) #define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" #define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PDO_SQLSRV_MORE_ERRORS "report_additional_errors" #define INI_PREFIX "pdo_sqlsrv." #ifndef _WIN32 @@ -64,6 +65,7 @@ PHP_INI_BEGIN() zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SQLSRV_MORE_ERRORS, "1", PHP_INI_ALL, OnUpdateLong, report_additional_errors, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals) #ifndef _WIN32 STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index adb4a5500..96a322ebd 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -776,6 +776,7 @@ struct sqlsrv_error_const { // subclass which is used by the core layer to instantiate ODBC errors struct sqlsrv_error : public sqlsrv_error_const { + struct sqlsrv_error *next; sqlsrv_error( void ) { @@ -783,16 +784,18 @@ struct sqlsrv_error : public sqlsrv_error_const { native_message = NULL; native_code = -1; format = false; + next = NULL; } - sqlsrv_error( _In_ SQLCHAR* sql_state, _In_ SQLCHAR* message, _In_ SQLINTEGER code, _In_ bool printf_format = false ) + sqlsrv_error( _In_ SQLCHAR* sql_state, _In_ SQLCHAR* message, _In_ SQLINTEGER code, _In_ bool printf_format = false) { - sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_ERROR_MESSAGE_LENGTH + 1 )); - strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast( message )); + sqlstate = reinterpret_cast(sqlsrv_malloc(SQL_SQLSTATE_BUFSIZE)); + native_message = reinterpret_cast(sqlsrv_malloc(SQL_MAX_ERROR_MESSAGE_LENGTH + 1)); + strcpy_s(reinterpret_cast(sqlstate), SQL_SQLSTATE_BUFSIZE, reinterpret_cast(sql_state)); + strcpy_s(reinterpret_cast(native_message), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast(message)); native_code = code; format = printf_format; + next = NULL; } sqlsrv_error( _In_ sqlsrv_error_const const& prototype ) @@ -802,16 +805,26 @@ struct sqlsrv_error : public sqlsrv_error_const { ~sqlsrv_error( void ) { - if( sqlstate != NULL ) { - sqlsrv_free( sqlstate ); + reset(); + } + + void reset() { + if (sqlstate != NULL) { + sqlsrv_free(sqlstate); + sqlstate = NULL; + } + if (native_message != NULL) { + sqlsrv_free(native_message); + native_message = NULL; } - if( native_message != NULL ) { - sqlsrv_free( native_message ); + if (next != NULL) { + next->reset(); + sqlsrv_free(next); + next = NULL; } } }; - // an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { @@ -852,7 +865,6 @@ class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + var_dump($stmt->fetchColumn()); + + echo "Exception should have been thrown!\n"; + + unset($stmt); + unset($conn); + } catch (PDOException $e) { + if ($on) { + compare2ErrorInfo($e->errorInfo); + } + else { + compareErrorInfo($e->errorInfo, $errorInfo); + } + } +} + +function checkWarning($conn) +{ + global $tsql, $errorInfo; + + try { + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + compareErrorInfo($stmt->errorInfo(), $errorInfo); + + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo "Do not expect exception\n"; + var_dump($e); + } +} + +try { + $conn = new PDO("sqlsrv:server=$server;", $uid, $pwd); + checkWarning($conn); + checkException($conn, 1); + checkException($conn, 0); + +} catch (PDOException $e) { + var_dump($e); +} + +echo "\nDone\n"; +?> +--EXPECTREGEX-- +Warning: PDOStatement::execute\(\): SQLSTATE\[01000\]: Warning: 5701 \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to '.+'. in .+(\/|\\)pdo_924_display_more_errors.php on line [0-9]+ + +Done diff --git a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt new file mode 100644 index 000000000..e1587b016 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt @@ -0,0 +1,239 @@ +--TEST-- +GitHub issue 924 - verifies all error messages are logged to a log file +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $conn; +} + +function printCursor($cursorArray) +{ + if ($cursorArray[PDO::ATTR_CURSOR] == PDO::CURSOR_FWDONLY) { + $cursor = 'FORWARD ONLY cursor'; + } else { + switch ($cursorArray[PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE]) { + case PDO::SQLSRV_CURSOR_DYNAMIC: + $cursor = 'server side DYNAMIC cursor'; + break; + case PDO::SQLSRV_CURSOR_STATIC: + $cursor = 'server side STATIC cursor'; + break; + case PDO::SQLSRV_CURSOR_KEYSET: + $cursor = 'server side KEYSET cursor'; + break; + case PDO::SQLSRV_CURSOR_BUFFERED: + $cursor = 'client side BUFFERED cursor'; + break; + default: + $cursor = 'error'; + break; + } + } + + echo "#####Testing $cursor#####\n"; + return $cursor; +} + +function checkResults($data, $results, $resultSet, $expectedRows) +{ + $failed = false; + for ($j = 0; $j < $expectedRows; $j++) { + if ($results[$j][0] != $data[$resultSet][$j]) { + $failed = true; + echo "Fetched results unexpected at row $j:\n"; + print_r($results[$j]); + break; + } + } + + return $failed; +} + +try { + ini_set('log_errors', '1'); + + $logFilename = 'php_924_cursors.log'; + $logFilepath = dirname(__FILE__).'/'.$logFilename; + + if (file_exists($logFilepath)) { + unlink($logFilepath); + } + + ini_set('error_log', $logFilepath); + ini_set('pdo_sqlsrv.log_severity', '1'); // errors only + + // All supported cursor types + $cursors = array(array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_DYNAMIC), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_STATIC), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_KEYSET), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED), + ); + + + // Data for testing, all integer types + $data = array(array(86, -217483648, 0, -432987563, 7, 217483647), + array(0, 31, 127, 255, 1, 10), + array(4534, -212, 32767, 0, 7, -32768), + array(-1, 546098342985600, 9223372000000000000, 5115115115115, 7, -7), + array(0, 1, 0, 0, 1, 1), + ); + + $tableName = 'pdo_924_batchquery_test'; + + // Column names + $colName = array('c1_int', 'c2_tinyint', 'c3_smallint', 'c4_bigint', 'c5_bit'); + $columns = array(new ColumnMeta('int', $colName[0]), + new ColumnMeta('tinyint', $colName[1]), + new ColumnMeta('smallint',$colName[2]), + new ColumnMeta('bigint', $colName[3]), + new ColumnMeta('bit', $colName[4])); + + $conn = toConnect(); + createTable($conn, $tableName, $columns); + + $expectedRows = sizeof($data[0]); + + // Expected result sets = number of columns, since the batch fetches each column sequentially + $expectedResultSets = count($colName); + + // Insert each row. Need an associative array to use insertRow() + for ($i = 0; $i < $expectedRows; ++$i) { + $inputs = array(); + for ($j = 0; $j < $expectedResultSets; ++$j) { + $inputs[$colName[$j]] = $data[$j][$i]; + } + + $stmt = insertRow($conn, $tableName, $inputs); + unset($stmt); + } + + $query = "SELECT c1_int FROM $tableName; + SELECT c2_tinyint FROM $tableName; + SELECT c3_smallint FROM $tableName; + SELECT c4_bigint FROM $tableName; + SELECT c5_bit FROM $tableName;"; + + for ($i = 0; $i < sizeof($cursors); ++$i) { + $cursorType = $cursors[$i]; + // $cursor = printCursor($i); + $cursor = printCursor($cursorType); + + $stmt = $conn->prepare($query, $cursorType); + $stmt->execute(); + + $numResultSets = 0; + do { + $res = $stmt->fetchAll(PDO::FETCH_NUM); + $failed = checkResults($data, $res, $numResultSets, $expectedRows); + ++$numResultSets; + } while (!$failed && $stmt->nextRowset()); + + if ($numResultSets != $expectedResultSets) { + echo ("Unexpected number of result sets, expected $expectedResultedSets, got $numResultSets\n"); + break; + } + + if (file_exists($logFilepath)) { + echo file_get_contents($logFilepath); + unlink($logFilepath); + } + + unset($stmt); + echo "#####Finished testing with $cursor#####\n"; + } + + // Now reset logging by disabling it + ini_set('pdo_sqlsrv.log_severity', '0'); + + dropTable($conn, $tableName); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +echo "Done.\n"; + +?> +--EXPECTF-- +#####Testing FORWARD ONLY cursor##### +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to 'PHPDatabase'. +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to us_english. +#####Finished testing with FORWARD ONLY cursor##### +#####Testing server side DYNAMIC cursor##### +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 16954 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Executing SQL directly; no cursor. +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +#####Finished testing with server side DYNAMIC cursor##### +#####Testing server side STATIC cursor##### +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 16954 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Executing SQL directly; no cursor. +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +#####Finished testing with server side STATIC cursor##### +#####Testing server side KEYSET cursor##### +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 16954 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Executing SQL directly; no cursor. +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +#####Finished testing with server side KEYSET cursor##### +#####Testing client side BUFFERED cursor##### +#####Finished testing with client side BUFFERED cursor##### +Done. \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt b/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt index 46ea1cdfc..762537a60 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt @@ -44,6 +44,9 @@ try { } else { echo "$logFilepath is missing!\n"; } + + // Now reset logging by disabling it + ini_set('pdo_sqlsrv.log_severity', '0'); echo "Done\n"; } catch (Exception $e) { @@ -56,6 +59,9 @@ try { [%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 [%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 [%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to %s. [%s UTC] pdo_sqlsrv_dbh_prepare: entering [%s UTC] pdo_sqlsrv_stmt_execute: entering [%s UTC] pdo_sqlsrv_stmt_describe_col: entering diff --git a/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt index a0169696f..239a8c509 100644 --- a/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt @@ -96,6 +96,9 @@ Done with 0 [%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02 [%s UTC] pdo_sqlsrv_stmt_execute: error code = 208 [%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 8180 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Statement(s) could not be prepared. Done with 1 [%s UTC] PHP Warning: PDO::query(): SQLSTATE[42S02]: Base table or view not found: 208 %s[SQL Server]Invalid object name 'temp_table'. in %spdo_errorMode_logs.php on line %d @@ -112,5 +115,8 @@ Done with 4 [%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02 [%s UTC] pdo_sqlsrv_stmt_execute: error code = 208 [%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 8180 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Statement(s) could not be prepared. Done with -1 diff --git a/test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt b/test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt new file mode 100644 index 000000000..e7b14c421 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt @@ -0,0 +1,43 @@ +--TEST-- +Error checking for multiple active row sets (MARS) disabled +--DESCRIPTION-- +This is similar to sqlsrv srv_053_mars_disabled_error_checks.phpt to check the errors +when multiple active row sets (MARS) is disabled. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $sql1 = "SELECT 'ONE'"; + $sql2 = "SELECT 'TWO'"; + + $stmt1 = $conn->query($sql1); + $stmt2 = $conn->query($sql2); + $res = [$stmt1->fetch(), $stmt2->fetch()]; + var_dump($res); + + unset($stmt1); + unset($stmt2); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +echo "\nDone\n"; +?> +--EXPECT-- +array(3) { + [0]=> + string(5) "IMSSP" + [1]=> + int(-61) + [2]=> + string(313) "The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option." +} + +Done From 1305d382422cf2d227dd592f7a3b6068230a3516 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 4 May 2020 17:29:06 -0700 Subject: [PATCH 02/12] Modified some tests --- test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt | 2 +- test/functional/pdo_sqlsrv/pdo_error.phpt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt index e1587b016..a0cd86304 100644 --- a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt +++ b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt @@ -1,5 +1,5 @@ --TEST-- -GitHub issue 924 - verifies all error messages are logged to a log file +GitHub issue 924 - verifies the warnings or error messages are logged to a log file --SKIPIF-- --FILE-- diff --git a/test/functional/pdo_sqlsrv/pdo_error.phpt b/test/functional/pdo_sqlsrv/pdo_error.phpt index 65ddce7e4..f26605459 100644 --- a/test/functional/pdo_sqlsrv/pdo_error.phpt +++ b/test/functional/pdo_sqlsrv/pdo_error.phpt @@ -29,4 +29,7 @@ Array \[0\] => 42S22 \[1\] => 207 \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'IntColX'\. + \[3\] => 42000 + \[4\] => 8180 + \[5\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \ No newline at end of file From b26329bcd02df9349003c228e8fac43b65af5e82 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 4 May 2020 17:58:47 -0700 Subject: [PATCH 03/12] Removed obsolete code and added comments --- source/pdo_sqlsrv/pdo_util.cpp | 21 +++++++++------------ source/shared/core_sqlsrv.h | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index fc6af69b4..9a1a1a478 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -475,14 +475,13 @@ bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned sqlsrv_error_auto_ptr error; format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args); - //strcpy_s(dbh->error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); - + // error_mode is valid because PDO API has already taken care of invalid ones if (!warning && dbh->error_mode == PDO_ERRMODE_EXCEPTION) { pdo_sqlsrv_throw_exception(error); } ctx.set_last_error(error); - + // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count // return error ignored = true for warnings. return (warning ? true : false); @@ -498,9 +497,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned sqlsrv_error_auto_ptr error; format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args); - //SQLSRV_ASSERT(strnlen_s(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); - //strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); - + // error_mode is valid because PDO API has already taken care of invalid ones if (!warning) { if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) { pdo_sqlsrv_throw_exception(error); @@ -517,7 +514,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned } ctx.set_last_error(error); - + // return error ignored = true for warnings. return (warning ? true : false); } @@ -532,13 +529,11 @@ bool pdo_sqlsrv_handle_stmt_error(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned sqlsrv_error_auto_ptr error; format_or_get_all_errors(ctx, sqlsrv_error_code, error, pdo_stmt->error_code, print_args); - //SQLSRV_ASSERT(strnlen_s(reinterpret_cast(error->sqlstate)) <= sizeof(pdo_stmt->error_code), "Error code overflow"); - //strcpy_s(pdo_stmt->error_code, sizeof(pdo_stmt->error_code), reinterpret_cast(error->sqlstate)); - + // error_mode is valid because PDO API has already taken care of invalid ones if (!warning && pdo_stmt->dbh->error_mode == PDO_ERRMODE_EXCEPTION) { pdo_sqlsrv_throw_exception(error); - } - ctx.set_last_error(error); + } + ctx.set_last_error(error); // return error ignored = true for warnings. return (warning ? true : false); @@ -662,6 +657,8 @@ void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int } while (result); } + // core_sqlsrv_get_odbc_error() returns the error_code of size SQL_SQLSTATE_BUFSIZE, + // which is the same size as pdo_error_type strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); } } diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 96a322ebd..8937a811b 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -776,7 +776,7 @@ struct sqlsrv_error_const { // subclass which is used by the core layer to instantiate ODBC errors struct sqlsrv_error : public sqlsrv_error_const { - struct sqlsrv_error *next; + struct sqlsrv_error *next; // Only used in pdo_sqlsrv for additional errors (as a linked list) sqlsrv_error( void ) { @@ -818,7 +818,7 @@ struct sqlsrv_error : public sqlsrv_error_const { native_message = NULL; } if (next != NULL) { - next->reset(); + next->reset(); // free the next sqlsrv_error, and so on sqlsrv_free(next); next = NULL; } From fc40a37f012d86333a456bbd18f3f304adc6e2b2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 5 May 2020 08:59:16 -0700 Subject: [PATCH 04/12] Fixed test error --- test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt index a0cd86304..edf207982 100644 --- a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt +++ b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt @@ -169,7 +169,7 @@ echo "Done.\n"; #####Testing FORWARD ONLY cursor##### [%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 [%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 -[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to 'PHPDatabase'. +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to '%s'. [%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 [%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 [%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to us_english. From fc1692db44493ea38c676d876ce5e3bbbb6d90a5 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 5 May 2020 10:04:13 -0700 Subject: [PATCH 05/12] Updated appveyor codecov --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index cf87f520f..a62f06b43 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -122,7 +122,7 @@ test_script: - ps: >- If ($env:BUILD_PLATFORM -Match "x86") { Write-Host "Running phpt tests via OpenCppCoverage..." - OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; + OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; Write-Host "Showing the last 25 lines of the log file..." Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -Tail 25; ls *.xml @@ -146,7 +146,7 @@ test_script: Write-Host "Showing the packages..."; Select-String package .\coverage.xml; Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh - bash codecov.sh -f "coverage.xml" + bash codecov.sh -f "coverage.xml" -y ${env:APPVEYOR_BUILD_FOLDER}\codecov.yml cd ${env:PHP_EXE_PATH} } From fe91c20db98f1c63926bf43a41f36ab36d638ad4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 5 May 2020 12:30:20 -0700 Subject: [PATCH 06/12] Expanded tests to check errorInfo --- appveyor.yml | 3 +- .../pdo_924_display_more_errors.phpt | 57 +++++++++++++++---- test/functional/pdo_sqlsrv/pdo_error.phpt | 33 ++++++++--- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a62f06b43..8f81e3d47 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -99,6 +99,7 @@ install: - echo install opencppcoverage - choco install opencppcoverage - set path=C:\Program Files\OpenCppCoverage;%PYTHON%;%PYTHON%\Scripts;%path% + - copy %APPVEYOR_BUILD_FOLDER%\codecov.yml c:\projects build_script: - copy %APPVEYOR_BUILD_FOLDER%\buildscripts\*.py c:\projects @@ -146,7 +147,7 @@ test_script: Write-Host "Showing the packages..."; Select-String package .\coverage.xml; Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh - bash codecov.sh -f "coverage.xml" -y ${env:APPVEYOR_BUILD_FOLDER}\codecov.yml + bash codecov.sh -f "coverage.xml" cd ${env:PHP_EXE_PATH} } diff --git a/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt index 67599f6d6..ce3fe6041 100644 --- a/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt @@ -55,22 +55,27 @@ function checkException($conn, $on) var_dump($stmt->fetchColumn()); echo "Exception should have been thrown!\n"; - - unset($stmt); - unset($conn); } catch (PDOException $e) { + // compare errorInfo arrays from both the exception object and the stmt object if ($on) { compare2ErrorInfo($e->errorInfo); + compare2ErrorInfo($stmt->errorInfo()); } else { compareErrorInfo($e->errorInfo, $errorInfo); + compareErrorInfo($stmt->errorInfo(), $errorInfo); } } + + unset($stmt); + unset($conn); } -function checkWarning($conn) +function checkWarning($conn, $on) { - global $tsql, $errorInfo; + global $tsql, $errorInfo, $errorInfo2; + + ini_set('pdo_sqlsrv.report_additional_errors', $on); try { $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); @@ -78,28 +83,56 @@ function checkWarning($conn) $stmt->execute(); compareErrorInfo($stmt->errorInfo(), $errorInfo); - - unset($stmt); - unset($conn); + if ($on) { + compareErrorInfo($stmt->errorInfo(), $errorInfo2, 3); + } else { + echo count($stmt->errorInfo()) . PHP_EOL; + } } catch (PDOException $e) { - echo "Do not expect exception\n"; + echo " Warnings are logged but do not expect exceptions.\n"; var_dump($e); } + + unset($stmt); + unset($conn); } try { + // This forces PHP to log errors rather than displaying errors on screen + ini_set('display_errors', '0'); + ini_set('log_errors', '1'); + + $logFilename = 'php_924_errors.log'; + $logFilepath = dirname(__FILE__).'/'.$logFilename; + + if (file_exists($logFilepath)) { + unlink($logFilepath); + } + + ini_set('error_log', $logFilepath); + ini_set('pdo_sqlsrv.log_severity', '2'); // warnings only + $conn = new PDO("sqlsrv:server=$server;", $uid, $pwd); - checkWarning($conn); + checkWarning($conn, 1); checkException($conn, 1); + checkWarning($conn, 0); checkException($conn, 0); + if (file_exists($logFilepath)) { + echo file_get_contents($logFilepath); + unlink($logFilepath); + } else { + echo "Expected to find the log file\n"; + } } catch (PDOException $e) { var_dump($e); } echo "\nDone\n"; ?> ---EXPECTREGEX-- -Warning: PDOStatement::execute\(\): SQLSTATE\[01000\]: Warning: 5701 \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to '.+'. in .+(\/|\\)pdo_924_display_more_errors.php on line [0-9]+ +--EXPECTF-- +3 +[%s UTC] PHP Warning: PDOStatement::execute(): SQLSTATE[01000]: Warning: 5701 %s[SQL Server]Changed database context to '%s'. in %spdo_924_display_more_errors.php on line %d +[%s UTC] PHP Warning: PDOStatement::execute(): SQLSTATE[01000]: Warning: 5701 %s[SQL Server]Changed database context to '%s'. in %spdo_924_display_more_errors.php on line %d Done diff --git a/test/functional/pdo_sqlsrv/pdo_error.phpt b/test/functional/pdo_sqlsrv/pdo_error.phpt index f26605459..3b3b4ac68 100644 --- a/test/functional/pdo_sqlsrv/pdo_error.phpt +++ b/test/functional/pdo_sqlsrv/pdo_error.phpt @@ -5,21 +5,36 @@ Test the PDO::errorCode() and PDO::errorInfo() methods. --FILE-- query("SELECT * FROM $tbname WHERE IntColX = 1"); + $tbname = "PDO_test_error"; + + // create a dummy table + createTable($db, $tbname, array(new ColumnMeta("int", "id"))); + + try { + // query with a wrong column name -- catch the exception and show errors + $stmt = $db->query("SELECT * FROM $tbname WHERE IntColX = 1"); + echo "Should have thrown an exception!\n"; + } catch (PDOException $e) { + echo $db->errorCode() . PHP_EOL; + if ($e->getCode() != $db->errorCode()) { + echo "Error codes do not match!\n"; + echo $e->getCode() . PHP_EOL; + } + $info = $db->errorInfo(); + print_r($info); + if ($e->errorInfo != $info) { + echo "Error info arrays do not match!\n"; + print_r($e->errorInfo); + } + } dropTable($db, $tbname); - unset($conn); + unset($db); } catch (PDOException $e) { - print($db->errorCode()); - echo "\n"; - print_r($db->errorInfo()); + var_dump($e); } ?> --EXPECTREGEX-- From 9910cc571be3adad6b17329f7b12d24762842820 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 May 2020 16:17:26 -0700 Subject: [PATCH 07/12] Polished pdo_util and updated the pooling test --- source/pdo_sqlsrv/pdo_util.cpp | 14 +++-- .../pdo_sqlsrv/PDO_ConnPool_Unix.phpt | 6 +- test/functional/pdo_sqlsrv/isPooled.php | 62 +++++++++++++++++-- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 9a1a1a478..2b6755f2c 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -43,7 +43,7 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code); // build the object and throw the PDO exception -void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ); +void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error); void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args); @@ -578,7 +578,7 @@ sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code) return error_message; } -void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) +void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error) { zval ex_obj; ZVAL_UNDEF( &ex_obj ); @@ -605,7 +605,7 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) add_next_index_long( &ex_error_info, error->native_code ); add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); - add_extra_errors_to_array(reinterpret_cast(error), &ex_error_info); + add_extra_errors_to_array(error, &ex_error_info); //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) //and the refcount of the zend_array is incremented by 1 @@ -624,9 +624,13 @@ void add_extra_errors_to_array(_In_ sqlsrv_error const* error, _Inout_ zval* arr if (error->next != NULL && PDO_SQLSRV_G(report_additional_errors)) { sqlsrv_error *p = error->next; while (p != NULL) { - add_next_index_string(array_z, reinterpret_cast(p->sqlstate)); + // check if sql state or native message is NULL and handle them accordingly + char * state = (p->sqlstate == NULL) ? "" : reinterpret_cast(p->sqlstate); + char * msg = (p->native_message == NULL) ? "" : reinterpret_cast(p->native_message); + + add_next_index_string(array_z, state); add_next_index_long(array_z, p->native_code); - add_next_index_string(array_z, reinterpret_cast(p->native_message)); + add_next_index_string(array_z, msg); p = p-> next; } diff --git a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt index c68a46b14..9d242ad58 100644 --- a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt +++ b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt @@ -3,7 +3,11 @@ PDO_SQLSRV Connection Pooling Test on Unix --DESCRIPTION-- This test assumes the default odbcinst.ini has not been modified. --SKIPIF-- - + --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $conn2->prepare("SET NOCOUNT ON; USE tempdb; SELECT 1/0 AS col1"); + $stmt->execute(); + } catch (PDOException $e) { + checkErrorInfo($stmt, $e); + } +} + +unset($conn2); + +function connectionID($conn) { $tsql = "SELECT [connection_id] FROM [sys].[dm_exec_connections] where session_id = @@SPID"; $stmt = $conn->query($tsql); @@ -23,4 +37,42 @@ function ConnectionID($conn) $stmt = null; return ($connID); } + +function isAzure($conn) +{ + try { + $tsql = "SELECT SERVERPROPERTY ('edition')"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + + if ($edition === "SQL Azure") { + return true; + } else { + return false; + } + } catch (PDOException $e) { + echo $e->getMessage(); + die("Could not fetch server property."); + } +} + +function checkErrorInfo($stmt, $err) +{ + $expected = "*Divide by zero error encountered*"; + $idx = count($err->errorInfo) - 1; + $failed = false; + if ($idx != 5 || !fnmatch($expected, $err->errorInfo[$idx])) { + echo "Error message unexpected!\n"; + $failed = true; + } + if ($err->errorInfo !== $stmt->errorInfo()) { + echo "Error info arrays should match!\n"; + $failed = true; + } + if ($failed) { + var_dump($err); + } +} ?> From 727bf702b3c10c9b6b32314b9960f3f1f6737d7f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 May 2020 16:35:34 -0700 Subject: [PATCH 08/12] Fixed compilation errors --- source/pdo_sqlsrv/pdo_util.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 2b6755f2c..4c121457b 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -625,8 +625,15 @@ void add_extra_errors_to_array(_In_ sqlsrv_error const* error, _Inout_ zval* arr sqlsrv_error *p = error->next; while (p != NULL) { // check if sql state or native message is NULL and handle them accordingly - char * state = (p->sqlstate == NULL) ? "" : reinterpret_cast(p->sqlstate); - char * msg = (p->native_message == NULL) ? "" : reinterpret_cast(p->native_message); + char *state = ""; + char *msg = ""; + + if (p->sqlstate != NULL) { + state = reinterpret_cast(p->sqlstate); + } + if (p->native_message != NULL) { + msg = reinterpret_cast(p->native_message); + } add_next_index_string(array_z, state); add_next_index_long(array_z, p->native_code); From 3593b408dbd5e362cf3faa1be82221ce54e5cbcf Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 May 2020 18:04:47 -0700 Subject: [PATCH 09/12] Fixed BVT errorinfo tests --- test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt | 5 ++++- test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt index 6f8e4bc66..5c958b6ec 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt @@ -20,5 +20,8 @@ Array \( \[0\] => 42S02 \[1\] => 208 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid object name 'Person.Addressx'. + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid object name 'Person.Addressx'\. + \[3\] => 42000 + \[4\] => 8180 + \[5\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt index 9252ad32c..62c112f99 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt @@ -23,4 +23,7 @@ Array \[0\] => 42S22 \[1\] => 207 \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'Cityx'. + \[3\] => 42000 + \[4\] => 8180 + \[5\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \ No newline at end of file From 7c0a64376ecb7b0a1472cf8d1d148dbb16e17a42 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 7 May 2020 12:30:21 -0700 Subject: [PATCH 10/12] Added check to prevent infinite loop --- source/pdo_sqlsrv/pdo_util.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 4c121457b..b0efeab59 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -661,9 +661,14 @@ void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int do { result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR); if (result) { + if (err->native_code == -1 && !strcmp("00000", reinterpret_cast(err->sqlstate))) { + result = false; + } + else { p->next = err.get(); err.transferred(); p = p->next; + } } } while (result); } From 7530a985e451d3dca0dc929ec2009436a983b403 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 7 May 2020 14:17:53 -0700 Subject: [PATCH 11/12] Removed the workaround for mac users --- source/pdo_sqlsrv/pdo_util.cpp | 7 +------ source/shared/core_util.cpp | 14 +++----------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index b0efeab59..ed10ebe99 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -661,15 +661,10 @@ void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int do { result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR); if (result) { - if (err->native_code == -1 && !strcmp("00000", reinterpret_cast(err->sqlstate))) { - result = false; - } - else { p->next = err.get(); err.transferred(); p = p->next; - } - } + } } while (result); } diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 417b70274..3c40df311 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -297,17 +297,9 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, SQL_MAX_ERROR_MESSAGE_LENGTH + 1, &wmessage_len ); // don't use the CHECK* macros here since it will trigger reentry into the error handling system - // Workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV). - // Instead of returning false, we return an empty error message to prevent the driver from throwing an exception. - // To reproduce: - // Create a connection and close it (return it to the pool) - // Create a new connection from the pool. - // Prepare and execute a statement that generates an info message (such as 'USE tempdb;') -#ifdef __APPLE__ - if( r == SQL_NO_DATA && ctx.driver() != NULL /*PDO SQLSRV*/ ) { - r = SQL_SUCCESS; - } -#endif // __APPLE__ + // removed the workaround for Mac users with unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV), for two reasons: + // (1) not recommended to use connection pooling with unixODBC < 2.3.7 + // (2) the problem was not reproducible with unixODBC 2.3.7 if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { return false; } From 0c812139222f6a06a5c01df00cccc57ff96fb686 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 12 May 2020 13:46:10 -0700 Subject: [PATCH 12/12] Renamed a function and check warnings vs errors --- source/pdo_sqlsrv/pdo_util.cpp | 12 ++++++------ source/shared/core_sqlsrv.h | 2 +- source/shared/core_util.cpp | 12 ++++++++++-- .../pdo_924_display_more_errors.phpt | 18 ++++++++++++++++++ .../pdo_sqlsrv/pdo_924_log_all_warnings.phpt | 2 +- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index ed10ebe99..8cda4670d 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -47,7 +47,7 @@ void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error); void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args); -void add_extra_errors_to_array(_In_ sqlsrv_error const* error, _Inout_ zval* array_z); +void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z); } // pdo driver error messages @@ -551,7 +551,7 @@ void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Ou add_next_index_long( pdo_zval, last_error->native_code ); add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); - add_extra_errors_to_array(last_error, pdo_zval); + add_remaining_errors_to_array (last_error, pdo_zval); } } @@ -605,7 +605,7 @@ void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error) add_next_index_long( &ex_error_info, error->native_code ); add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); - add_extra_errors_to_array(error, &ex_error_info); + add_remaining_errors_to_array (error, &ex_error_info); //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) //and the refcount of the zend_array is incremented by 1 @@ -619,7 +619,7 @@ void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error) zend_throw_exception_object( &ex_obj ); } -void add_extra_errors_to_array(_In_ sqlsrv_error const* error, _Inout_ zval* array_z) +void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z) { if (error->next != NULL && PDO_SQLSRV_G(report_additional_errors)) { sqlsrv_error *p = error->next; @@ -651,7 +651,7 @@ void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); } else { - bool result = core_sqlsrv_get_odbc_error(ctx, 1, error, SEV_ERROR); + bool result = core_sqlsrv_get_odbc_error(ctx, 1, error, SEV_ERROR, true); if (result) { // Check if there exist more errors int rec_number = 2; @@ -659,7 +659,7 @@ void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error *p = error; do { - result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR); + result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR, true); if (result) { p->next = err.get(); err.transferred(); diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 8937a811b..0dae9a0ea 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1913,7 +1913,7 @@ enum error_handling_flags { // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, - _In_ logging_severity severity ); + _In_ logging_severity severity, _In_ bool check_warning = false ); // format and return a driver specfic error void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 3c40df311..2d0c9c589 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -257,8 +257,7 @@ void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* i // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity - ) +bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity, _In_ bool check_warning /* = false */) { SQLHANDLE h = ctx.handle(); SQLSMALLINT h_type = ctx.handle_type(); @@ -340,6 +339,15 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu break; } + // Only overrides 'severity' if 'check_warning' is true (false by default) + if (check_warning) { + // The character string value returned for an SQLSTATE consists of a two-character class value + // followed by a three-character subclass value. A class value of "01" indicates a warning. + // https://docs.microsoft.com/sql/odbc/reference/appendixes/appendix-a-odbc-error-codes?view=sql-server-ver15 + if (error->sqlstate[0] == '0' && error->sqlstate[1] == '1') { + severity = SEV_WARNING; + } + } // log the error first LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); diff --git a/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt index ce3fe6041..82d30b8b4 100644 --- a/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt @@ -132,7 +132,25 @@ echo "\nDone\n"; ?> --EXPECTF-- 3 +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to 'master'. +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to us_english. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. [%s UTC] PHP Warning: PDOStatement::execute(): SQLSTATE[01000]: Warning: 5701 %s[SQL Server]Changed database context to '%s'. in %spdo_924_display_more_errors.php on line %d +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. [%s UTC] PHP Warning: PDOStatement::execute(): SQLSTATE[01000]: Warning: 5701 %s[SQL Server]Changed database context to '%s'. in %spdo_924_display_more_errors.php on line %d +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. Done diff --git a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt index edf207982..d7bfb3313 100644 --- a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt +++ b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt @@ -70,7 +70,7 @@ try { } ini_set('error_log', $logFilepath); - ini_set('pdo_sqlsrv.log_severity', '1'); // errors only + ini_set('pdo_sqlsrv.log_severity', '3'); // warnings and errors only // All supported cursor types $cursors = array(array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY),