//--------------------------------------------------------------------------------------------------------------------------------- // File: pdo_util.cpp // // Contents: Utility functions used by both connection or statement functions // // Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, // and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- #include "php_pdo_sqlsrv.h" #include "zend_exceptions.h" // *** internal constants *** namespace { const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nError Message: %3!s!\n"; const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s"; char EXCEPTION_PROPERTY_MSG[] = "message"; char EXCEPTION_PROPERTY_CODE[] = "code"; char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo"; const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative // the warning message is not the error message alone; it must take WARNING_TEMPLATE above into consideration without the formats const int WARNING_MIN_LENGTH = strlen(WARNING_TEMPLATE) - strlen("%1!s!%2!d!%3!s!"); // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; char log_msg[ LOG_MSG_SIZE ]; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; // Returns a sqlsrv_error for a given error code. sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code); // build the object and throw the PDO exception void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ); } // pdo driver error messages // errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0 pdo_error PDO_ERRORS[] = { { SQLSRV_ERROR_DRIVER_NOT_INSTALLED, { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 13 for SQL Server to " "communicate with SQL Server. Access the following URL to download the ODBC Driver 13 for SQL Server " "for %1!s!: " "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true } }, { SQLSRV_ERROR_ZEND_HASH, { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false } }, { PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams " "cannot be specified as output parameters.", -3, false } }, { SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point " "numbers, strings, and streams may be used as parameters.", -4, true } }, { SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true } }, { SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true } }, { SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true } }, { SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true } }, { SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true } }, { SQLSRV_ERROR_ZEND_STREAM, { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false } }, { SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true } }, { SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false } }, { SQLSRV_ERROR_FETCH_PAST_END, { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, " "no more data may be retrieved.", -13, false } }, { SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false } }, { SQLSRV_ERROR_NO_FIELDS, { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false } }, { SQLSRV_ERROR_FETCH_NOT_CALLED, { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch " "functions was called.", -16, false } }, { SQLSRV_ERROR_NO_DATA, { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true } }, { SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true } }, { SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false } }, { SQLSRV_ERROR_NEXT_RESULT_PAST_END, { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false } }, { SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" " escaped with another right brace (}}).", -21, false } }, { SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces " "must be escaped with another right brace (}}).", -22, true } }, { SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true } }, { SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true } }, { SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " "key/value pairs.", -25, false } }, { SQLSRV_ERROR_INVALID_CONNECTION_KEY, { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false } }, { SQLSRV_ERROR_INVALID_TYPE, { IMSSP, (SQLCHAR*) "Invalid type.", -27, false } }, { PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false } }, { SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true } }, { SQLSRV_ERROR_INVALID_OPTION_KEY, { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true } }, { SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true } }, { SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false } }, { PDO_SQLSRV_ERROR_INVALID_DBH_ATTR, { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false } }, { PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false } }, { PDO_SQLSRV_ERROR_INVALID_ENCODING, { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false } }, { PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants " "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false } }, { PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false } }, { PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false } }, { PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false } }, { PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false } }, { PDO_SQLSRV_ERROR_INVALID_DSN_STRING, {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false } }, { PDO_SQLSRV_ERROR_INVALID_DSN_KEY, { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true } }, { PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false } }, { PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or " "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false } }, { PDO_SQLSRV_ERROR_PARAM_PARSE, { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false } }, { PDO_SQLSRV_ERROR_LAST_INSERT_ID, { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false } }, { SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true } }, { PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only " "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true } }, { PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types " "can take an encoding option.", -49, true } }, { PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true } }, { PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an " "encoding option.", -51, true } }, { PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true } }, { PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the " "$driver_options array of PDO::prepare.", -53, false } }, { SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true } }, { SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output " "parameter %1!d!.", -55, true } }, { PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.", -56, true } }, { PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an " "explicit length.", -57, true } }, { PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false } }, { /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related errors so having a mapping here */ SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " "streams.", -59, false} }, { SQLSRV_ERROR_STREAM_CREATE, { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false } }, { SQLSRV_ERROR_MARS_OFF, { IMSSP, (SQLCHAR*)"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.", -61, false } }, { SQLSRV_ERROR_FIELD_INDEX_ERROR, { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its " "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true } }, { PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true } }, { PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false } }, { PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false } }, { PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .", -66, true } }, { PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.", -67, true } }, { PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of " "PDO::prepare.", -68, false } }, { PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to " "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false } }, { SQLSRV_ERROR_INVALID_BUFFER_LIMIT, { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not " "positive. Only positive numbers are valid for this attribute.", -70, false } }, { SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } }, { UINT_MAX, {} } }; // PDO error handler for the environment context. bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, 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; if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); } else { bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } 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 TSRMLS_CC ); } 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 ); } // pdo error handler for the dbh context. bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ) { pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); 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 TSRMLS_CC, print_args ); } else { bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } SQLSRV_ASSERT(strlen(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 TSRMLS_CC ); } ctx.set_last_error( error ); break; case PDO_ERRMODE_WARNING: if( !warning ) { size_t msg_len = strlen( 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, msg ); } 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; } // return error ignored = true for warnings. return ( warning ? true : false ); } // PDO error handler for the statement context. bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, 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" ); 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 TSRMLS_CC, print_args ); } else { bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } SQLSRV_ASSERT( strlen( 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 TSRMLS_CC ); } 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; } // return error ignored = true for warnings. 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 // 2, SQLSTATE of the error (driver specific error messages are 'IMSSP') void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ) { if( last_error ) { // 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 )); } } // Formats the error message and writes to the php error log. void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) { if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { return; } DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); // if an error occurs for FormatMessage, we just output an internal error occurred. if( rc == 0 ) { SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); } php_log_err( log_msg TSRMLS_CC ); } namespace { // Workaround for name collision problem between the SQLSRV and PDO_SQLSRV drivers on Mac // Place get_error_message into the anonymous namespace in pdo_util.cpp sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { sqlsrv_error_const *error_message = NULL; int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; if (zr == FAILURE) { DIE("get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code); } SQLSRV_ASSERT(error_message != NULL, "get_error_message: error_message was null"); return error_message; } void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) { zval ex_obj; ZVAL_UNDEF( &ex_obj ); zend_class_entry* ex_class = php_pdo_get_exception(); int zr = object_init_ex( &ex_obj, ex_class ); SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); sqlsrv_malloc_auto_ptr ex_msg; size_t ex_msg_len = strlen( 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 ); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, ex_msg TSRMLS_CC ); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, reinterpret_cast( error->sqlstate ) TSRMLS_CC ); zval ex_error_info; ZVAL_UNDEF( &ex_error_info ); array_init( &ex_error_info ); 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 )); //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, &ex_error_info TSRMLS_CC ); //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed Z_DELREF( ex_error_info ); zend_throw_exception_object( &ex_obj TSRMLS_CC ); } }