Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDO errorinfo includes additional odbc messages if available #1133

Merged
merged 12 commits into from
May 22, 2020
3 changes: 2 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
215 changes: 106 additions & 109 deletions source/pdo_sqlsrv/pdo_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ const int WARNING_MIN_LENGTH = static_cast<const int>( 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
Expand Down Expand Up @@ -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<pdo_dbh_t*>( ctx.driver());
SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" );

sqlsrv_error_auto_ptr error;

if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
SQLSRV_ASSERT((ctx != NULL), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null");
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>(ctx.driver());
SQLSRV_ASSERT((dbh != NULL), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null");

core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args );
}
else {
sqlsrv_error_auto_ptr error;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args);

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<const char*>( 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.
Expand All @@ -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;

if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {

core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args );
format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args);

// 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<const char*>(error->native_message)) + SQL_SQLSTATE_BUFSIZE
+ MAX_DIGITS + WARNING_MIN_LENGTH + 1;
sqlsrv_malloc_auto_ptr<char> msg;
msg = static_cast<char*>(sqlsrv_malloc(msg_len));
core_sqlsrv_format_message(msg, static_cast<unsigned int>(msg_len), WARNING_TEMPLATE, error->sqlstate, error->native_code,
error->native_message);
php_error(E_WARNING, "%s", msg.get());
}
}
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<const char*>(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow");
strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast<const char*>(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<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
+ MAX_DIGITS + WARNING_MIN_LENGTH + 1;
sqlsrv_malloc_auto_ptr<char> msg;
msg = static_cast<char*>( sqlsrv_malloc( msg_len ) );
core_sqlsrv_format_message( msg, static_cast<unsigned int>( 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<pdo_stmt_t*>( 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<pdo_stmt_t*>(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<const char*>( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow");
strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast<const char*>( 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
Expand All @@ -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<char*>( last_error->native_message ));

add_remaining_errors_to_array (last_error, pdo_zval);
}
}

Expand All @@ -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 );
Expand All @@ -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<char> ex_msg;
size_t ex_msg_len = strnlen_s( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE +
size_t ex_msg_len = strnlen_s(reinterpret_cast<const char*>(error->native_message)) + SQL_SQLSTATE_BUFSIZE +
12 + 1; // 12 = "SQLSTATE[]: "
ex_msg = reinterpret_cast<char*>( sqlsrv_malloc( ex_msg_len ));
snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message );
ex_msg = reinterpret_cast<char*>(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,
Expand All @@ -665,6 +604,9 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error )
add_next_index_string( &ex_error_info, reinterpret_cast<char*>( error->sqlstate ));
add_next_index_long( &ex_error_info, error->native_code );
add_next_index_string( &ex_error_info, reinterpret_cast<char*>( 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,
Expand All @@ -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<char*>(p->sqlstate);
}
if (p->native_message != NULL) {
msg = reinterpret_cast<char*>(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<const char*>(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<const char*>(error->sqlstate));
}
}

}
1 change: 1 addition & 0 deletions source/pdo_sqlsrv/php_pdo_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions source/pdo_sqlsrv/php_pdo_sqlsrv_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Loading