From 06d7a496ae98fa16939c6639ff0da0db4fc4a8d8 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 22 May 2020 15:09:28 -0700 Subject: [PATCH] PDO errorinfo includes additional odbc messages if available (#1133) --- appveyor.yml | 3 +- source/pdo_sqlsrv/pdo_util.cpp | 213 ++++++++-------- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 1 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 + source/shared/core_sqlsrv.h | 36 ++- source/shared/core_util.cpp | 26 +- .../msdn_pdoStatement_errorInfo.phpt | 5 +- test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt | 3 + .../pdo_sqlsrv/PDO_ConnPool_Unix.phpt | 6 +- test/functional/pdo_sqlsrv/isPooled.php | 62 ++++- .../pdo_924_display_more_errors.phpt | 156 ++++++++++++ .../pdo_sqlsrv/pdo_924_log_all_warnings.phpt | 239 ++++++++++++++++++ .../pdo_sqlsrv/pdo_connection_logs.phpt | 6 + test/functional/pdo_sqlsrv/pdo_error.phpt | 36 ++- .../pdo_sqlsrv/pdo_errorMode_logs.phpt | 6 + .../pdo_mars_disabled_error_checks.phpt | 43 ++++ 16 files changed, 693 insertions(+), 150 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/appveyor.yml b/appveyor.yml index cf87f520..8f81e3d4 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 @@ -122,7 +123,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 diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 58f49efa..8cda4670 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -43,8 +43,11 @@ 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); + +void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z); } // pdo driver error messages @@ -462,47 +465,26 @@ 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_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; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->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" ); + // 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); } - strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); + ctx.set_last_error(error); - 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; - } - // 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 +495,50 @@ 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; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->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" ); + // 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); + } + 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()); + } } - 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; - } + 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 )); - - 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; + // 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); // 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 +550,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_remaining_errors_to_array (last_error, pdo_zval); } } @@ -639,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 ); @@ -650,10 +589,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 +604,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_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 zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, @@ -677,4 +619,59 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) zend_throw_exception_object( &ex_obj ); } +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; + while (p != NULL) { + // check if sql state or native message is NULL and handle them accordingly + 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); + add_next_index_string(array_z, msg); + + 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, true); + 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, true); + if (result) { + p->next = err.get(); + err.transferred(); + p = p->next; + } + } 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/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 79a294f9..d7f5ac0c 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 abf711ce..e81cf09d 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 adb4a550..0dae9a0e 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; // Only used in pdo_sqlsrv for additional errors (as a linked list) 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 ); + if (native_message != NULL) { + sqlsrv_free(native_message); + native_message = NULL; + } + if (next != NULL) { + next->reset(); // free the next sqlsrv_error, and so on + 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 @@ public: } }; - //********************************************************************************************************************************* // Context //********************************************************************************************************************************* @@ -1901,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 417b7027..2d0c9c58 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(); @@ -297,17 +296,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; } @@ -348,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/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt index 6f8e4bc6..5c958b6e 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 9252ad32..62c112f9 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 diff --git a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt index c68a46b1..9d242ad5 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); + } +} ?> diff --git a/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt new file mode 100644 index 00000000..82d30b8b --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt @@ -0,0 +1,156 @@ +--TEST-- +GitHub issue 924 - Wrong error message after switching database context +--DESCRIPTION-- +Verifies that the user has the option to see the following error message after the first one. +--SKIPIF-- + +--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"; + } 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, $on) +{ + global $tsql, $errorInfo, $errorInfo2; + + ini_set('pdo_sqlsrv.report_additional_errors', $on); + + try { + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + compareErrorInfo($stmt->errorInfo(), $errorInfo); + if ($on) { + compareErrorInfo($stmt->errorInfo(), $errorInfo2, 3); + } else { + echo count($stmt->errorInfo()) . PHP_EOL; + } + } catch (PDOException $e) { + 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, 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"; +?> +--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 new file mode 100644 index 00000000..d7bfb331 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt @@ -0,0 +1,239 @@ +--TEST-- +GitHub issue 924 - verifies the warnings or 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', '3'); // warnings and 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 '%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. +#####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 46ea1cdf..762537a6 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_error.phpt b/test/functional/pdo_sqlsrv/pdo_error.phpt index 65ddce7e..3b3b4ac6 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-- @@ -29,4 +44,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 diff --git a/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt index a0169696..239a8c50 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 00000000..e7b14c42 --- /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