From c920eb693cc09a594e6f0a57e5ffbf744d475f70 Mon Sep 17 00:00:00 2001 From: icosahedron Date: Tue, 28 Apr 2009 21:49:01 +0000 Subject: [PATCH] SQL Server Driver for PHP 1.0 April 2009 Cumulative Update Please see the README.txt for informaton about changes made for this update. --- README.TXT | 8 +- conn.cpp | 87 ++++++++++++++++++--- init.cpp | 6 +- php_sqlsrv.h | 41 +++++++++- stmt.cpp | 215 +++++++++++++++++++++++++++++++++++++++------------ stream.cpp | 2 +- template.rc | 4 +- util.cpp | 13 +++- version.h | 8 +- 9 files changed, 305 insertions(+), 79 deletions(-) diff --git a/README.TXT b/README.TXT index 1d86a6db..c3b982f1 100644 --- a/README.TXT +++ b/README.TXT @@ -1,17 +1,17 @@ -* Notes about changes to the SQL Server 2005 Driver for PHP October 2008 Cumulative Update (CU) * +* Notes about changes to the SQL Server Driver for PHP April 2009 Cumulative Update (CU) * For details about the changes included in this CU, please see our blog at http://blogs.msdn.com/sqlphp or see the SQLServerDriverForPHP_Readme.htm file that is part of the download package. -* Notes about compiling the SQL Server 2005 Driver for PHP October 2008 CU * +* Notes about compiling the SQL Server Driver for PHP April 2009 CU * Prerequisites: You must first be able to build PHP without including -the SQL Server 2005 Driver for PHP extension. For help with doing this, see +the SQL Server Driver for PHP extension. For help with doing this, see the official PHP website, http://php.net. -To compile the SQL Server 2005 Driver for PHP: +To compile the SQL Server Driver for PHP: 1) Copy the source code into the ext\sqlsrv directory. diff --git a/conn.cpp b/conn.cpp index 1e3fed2d..ebd80d0a 100644 --- a/conn.cpp +++ b/conn.cpp @@ -40,11 +40,21 @@ const int INFO_BUFFER_LEN = 256; // number of segments in a version resource const int VERSION_SUBVERSIONS = 4; +// url table for driver links based on processor +struct _platform_url { + const char* platform; + const char* url; +} driver_info[] = { + { "x86", "http://go.microsoft.com/fwlink/?LinkId=137108" }, + { "x64", "http://go.microsoft.com/fwlink/?LinkId=137109" }, + { "ia64", "http://go.microsoft.com/fwlink/?LinkId=137110" } +}; // *** internal function prototypes *** sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, zval const* options_z, char const* _FN_ TSRMLS_DC ); SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, const char* server, zval const* options, __inout std::string& connection_string TSRMLS_DC ); +struct _platform_url* get_driver_info( void ); bool mark_params_by_reference( zval** params_zz, char const* _FN_ TSRMLS_DC ); void sqlsrv_conn_common_close( sqlsrv_conn* c, const char* function, bool check_errors TSRMLS_DC ); @@ -213,6 +223,19 @@ PHP_FUNCTION( sqlsrv_connect ) memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); conn_str.clear(); + if( !SQL_SUCCEEDED( r )) { + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); + // if the SQLSTATE is IM002, that means that the driver is not installed + if( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2' ) { + struct _platform_url* info = get_driver_info(); + handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_DRIVER_NOT_INSTALLED TSRMLS_CC, info->platform, info->url ); + SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle ); + conn->ctx.handle = SQL_NULL_HANDLE; + RETURN_FALSE; + } + } CHECK_SQL_ERROR( r, conn, _FN_, NULL, SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle ); conn->ctx.handle = SQL_NULL_HANDLE; RETURN_FALSE ); CHECK_SQL_WARNING( r, conn, _FN_, NULL ); @@ -237,7 +260,7 @@ PHP_FUNCTION( sqlsrv_connect ) // the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback // or sqlsrv_commit. // -// The SQL Server 2005 Driver for PHP is in auto-commit mode by default. This +// The SQL Server Driver for PHP is in auto-commit mode by default. This // means that all queries are automatically committed upon success unless they // have been designated as part of an explicit transaction by using // sqlsrv_begin_transaction. @@ -444,7 +467,7 @@ void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ) // connection to the auto-commit mode. The current transaction includes all // statements on the specified connection that were executed after the call to // sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or -// sqlsrv_commit. The SQL Server 2005 Driver for PHP is in auto-commit mode by +// sqlsrv_commit. The SQL Server Driver for PHP is in auto-commit mode by // default. This means that all queries are automatically committed upon success // unless they have been designated as part of an explicit transaction by using // sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is @@ -552,9 +575,10 @@ PHP_FUNCTION( sqlsrv_prepare ) stmt->prepared = true; - if( !mark_params_by_reference( ¶ms_z, _FN_ TSRMLS_CC )) { - RETURN_FALSE; - } + if( !mark_params_by_reference( ¶ms_z, _FN_ TSRMLS_CC )) { + free_odbc_resources( stmt TSRMLS_CC ); + RETURN_FALSE; + } stmt->params_z = params_z; @@ -639,12 +663,18 @@ PHP_FUNCTION( sqlsrv_query ) RETURN_FALSE; } - if( !mark_params_by_reference( ¶ms_z, _FN_ TSRMLS_CC )) { + // if it's not a NULL pointer and not an array, return an error + if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { free_odbc_resources( stmt TSRMLS_CC ); + handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } stmt->params_z = params_z; + // zval_add_ref released in free_odbc_resources + if( params_z ) { + zval_add_ref( ¶ms_z ); + } executed = sqlsrv_stmt_common_execute( stmt, sql_string, sql_len, true, _FN_ TSRMLS_CC ); @@ -687,7 +717,7 @@ PHP_FUNCTION( sqlsrv_query ) // statements on the specified connection that were executed after the call to // sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or // sqlsrv_commit. -// The SQL Server 2005 Driver for PHP is in auto-commit mode by default. This +// The SQL Server Driver for PHP is in auto-commit mode by default. This // means that all queries are automatically committed upon success unless they // have been designated as part of an explicit transaction by using // sqlsrv_begin_transaction. @@ -1116,7 +1146,9 @@ sqlsrv_stmt* allocate_stmt( __in sqlsrv_conn* conn, zval const* options_z, char stmt->current_parameter = NULL; stmt->current_parameter_read = 0; stmt->params_z = NULL; + stmt->params_ind_ptr = NULL; stmt->param_datetime_buffers = NULL; + stmt->param_output_strings = NULL; stmt->param_buffer = param_buffer; stmt->param_buffer_size = PHP_STREAM_BUFFER_SIZE; stmt->send_at_exec = true; @@ -1226,13 +1258,20 @@ bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS zend_hash_has_more_elements( params_ht ) == SUCCESS; zend_hash_move_forward( params_ht ), ++i ) { - zr = zend_hash_get_current_data_ex( params_ht, reinterpret_cast( ¶m ), NULL ); CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, zval_ptr_dtor( params_zz ); return false; ); // if it's a sole variable if( Z_TYPE_PP( param ) != IS_ARRAY ) { - (*param)->is_ref = 1; // mark it as a reference + if( !PZVAL_IS_REF( *param ) && Z_REFCOUNT_P( *param ) > 1 ) { + // 10 should be sufficient for adding up to a 3 digit number to the message + int warning_len = strlen( PHP_WARNING_VAR_NOT_REFERENCE->native_message ) + 10; + emalloc_auto_ptr warning; + warning = static_cast( emalloc( warning_len )); + snprintf( warning, warning_len, PHP_WARNING_VAR_NOT_REFERENCE->native_message, i ); + php_error( E_WARNING, warning ); + } + Z_SET_ISREF_PP( param ); // mark it as a reference } // else mark [0] as a reference else { @@ -1243,11 +1282,39 @@ bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS zval_ptr_dtor( params_zz ); return false; } - (*var)->is_ref = 1; + if( !PZVAL_IS_REF( *var ) && Z_REFCOUNT_P( *var ) > 1 ) { + // 10 should be sufficient for adding up to a 3 digit number to the message + int warning_len = strlen( PHP_WARNING_VAR_NOT_REFERENCE->native_message ) + 10; + emalloc_auto_ptr warning; + warning = static_cast( emalloc( warning_len )); + snprintf( warning, warning_len, PHP_WARNING_VAR_NOT_REFERENCE->native_message, i ); + php_error( E_WARNING, warning ); + } + Z_SET_ISREF_PP( var ); } } return true; } +struct _platform_url* get_driver_info( void ) +{ + SYSTEM_INFO sys_info; + + GetSystemInfo( &sys_info ); + + switch( sys_info.wProcessorArchitecture ) { + + case PROCESSOR_ARCHITECTURE_INTEL: + return &driver_info[0]; + case PROCESSOR_ARCHITECTURE_AMD64: + return &driver_info[1]; + case PROCESSOR_ARCHITECTURE_IA64: + return &driver_info[2]; + default: + DIE( "Unknown Windows processor architecture" ); + return NULL; + } +} + } // namespace diff --git a/init.cpp b/init.cpp index 58c9f9a2..ad8afd94 100644 --- a/init.cpp +++ b/init.cpp @@ -3,7 +3,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. // -// Contents: The initialization routines for the SQL Server 2005 Driver for PHP 1.0 +// Contents: The initialization routines for the SQL Server Driver for PHP 1.0 // // Comments: // @@ -495,9 +495,9 @@ PHP_RINIT_FUNCTION(sqlsrv) SQLSRV_G( henv_context )->ctx.handle = g_henv_cp; SQLSRV_G( henv_context )->ctx.handle_type = SQL_HANDLE_ENV; ALLOC_INIT_ZVAL( SQLSRV_G( errors )); - SQLSRV_G( errors )->is_ref = 1; + Z_SET_ISREF_PP( &SQLSRV_G( errors ) ); ALLOC_INIT_ZVAL( SQLSRV_G( warnings )); - SQLSRV_G( warnings )->is_ref = 1; + Z_SET_ISREF_PP( &SQLSRV_G( warnings ) ); // read INI settings SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS ); diff --git a/php_sqlsrv.h b/php_sqlsrv.h index 593d470a..e5bbd7a3 100644 --- a/php_sqlsrv.h +++ b/php_sqlsrv.h @@ -6,7 +6,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. // -// Contents: Declarations for the SQL Server 2005 Driver for PHP 1.0 +// Contents: Declarations for the SQL Server Driver for PHP 1.0 // // Comments: Also contains "internal" declarations shared across source files. // @@ -61,6 +61,10 @@ typedef int socklen_t; #pragma warning(pop) +#if PHP_MAJOR_VERSION > 5 || PHP_MAJOR_VERSION < 5 || ( PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 2 ) || ( PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 3 ) +#error Trying to compile "Microsoft SQL Server Driver for PHP" with an unsupported version of PHP +#endif + #if ZEND_DEBUG // debug build causes warning C4505 to pop up from the Zend header files #pragma warning( disable: 4505 ) @@ -102,6 +106,12 @@ struct sqlsrv_static_assert { static const int value = 1; }; #define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) +#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2 +#define Z_SET_ISREF_P( pzval ) ((pzval)->is_ref = 1) +#define Z_SET_ISREF_PP( ppzval ) Z_SET_ISREF_P(*(ppzval)) +#define Z_REFCOUNT_P( pzval ) ((pzval)->refcount) +#endif + //********************************************************************************************************************************** // Initialization Functions @@ -182,10 +192,21 @@ struct sqlsrv_fetch_field { unsigned int len; }; +// holds the string output parameter information +struct sqlsrv_output_string { + zval* string_z; + int param_num; // used to index into the params_ind_ptr of sqlsrv_stmt to get the length of the output string + + sqlsrv_output_string( zval* str_z, int param ) : string_z( str_z ), param_num( param ) + { + } +}; + // *** statement resource structure *** struct sqlsrv_stmt { - void new_result_set( bool release_datetime_buffers = true ); + void free_param_data( void ); + void new_result_set( void ); sqlsrv_context ctx; sqlsrv_conn* conn; @@ -201,7 +222,9 @@ struct sqlsrv_stmt { bool past_fetch_end; bool past_next_result_end; zval* params_z; + SQLINTEGER* params_ind_ptr; zval* param_datetime_buffers; + zval* param_output_strings; // list of output string parameters that need null terminating for proper length void* param_buffer; int param_buffer_size; bool send_at_exec; @@ -500,10 +523,15 @@ extern sqlsrv_error SQLSRV_ERROR_COMMIT_FAILED[]; extern sqlsrv_error SQLSRV_ERROR_ROLLBACK_FAILED[]; extern sqlsrv_error SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF[]; extern sqlsrv_error SQLSRV_ERROR_REGISTER_RESOURCE[]; +extern sqlsrv_error SQLSRV_ERROR_DRIVER_NOT_INSTALLED[]; // definitions for PHP specific warnings returned by sqlsrv extern sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[]; + +// definitios for PHP warnings returned via php_error +extern sqlsrv_error PHP_WARNING_VAR_NOT_REFERENCE[]; + enum error_handling_flags { SQLSRV_ERR_ERRORS, SQLSRV_ERR_WARNINGS, @@ -1073,6 +1101,15 @@ public: return sqlsrv_auto_ptr::operator=( ptr ); } +#if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3) + // New for 5.3. 5.3.x now uses an augmented zval, called a zval_gc_info which contains + // information about the gc buffer it was allocated from + operator zval_gc_info*() + { + return reinterpret_cast( _ptr ); + } +#endif + private: zval_auto_ptr( const zval_auto_ptr& src ); diff --git a/stmt.cpp b/stmt.cpp index 5f096b2e..6c94fecc 100644 --- a/stmt.cpp +++ b/stmt.cpp @@ -40,11 +40,11 @@ const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); -// constants for maximums in SQL Server 2005 -const int SQL_SERVER_2005_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_2005_MAX_PRECISION = 38; -const int SQL_SERVER_2005_DEFAULT_PRECISION = 18; -const int SQL_SERVER_2005_DEFAULT_SCALE = 0; +// constants for maximums in SQL Server +const int SQL_SERVER_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_MAX_PRECISION = 38; +const int SQL_SERVER_DEFAULT_PRECISION = 18; +const int SQL_SERVER_DEFAULT_SCALE = 0; // base allocation size when retrieving a string field const int INITIAL_FIELD_STRING_LEN = 256; @@ -69,6 +69,7 @@ const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1; // *** internal function prototypes *** // These are arranged alphabetically. They are all used by the sqlsrv statement functions. +bool adjust_output_string_lengths( sqlsrv_stmt* stmt, const char* _FN_ TSRMLS_DC ); SQLSMALLINT binary_or_char_encoding( SQLSMALLINT c_type ); bool check_for_next_stream_parameter( sqlsrv_stmt* stmt, zval* return_value TSRMLS_DC ); void close_active_stream( sqlsrv_stmt* s TSRMLS_DC ); @@ -81,6 +82,7 @@ sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value void fetch_common( sqlsrv_stmt* stmt, int fetch_type, zval* return_value, const char* _FN_, bool allow_empty_field_names TSRMLS_DC ); void get_field_common( sqlsrv_stmt* s, const char* _FN_, sqlsrv_phptype sqlsrv_phptype, SQLUSMALLINT field_index, zval**field_value TSRMLS_DC ); void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT field_index, zval* return_value, const char* _FN_ TSRMLS_DC ); +SQLRETURN has_rows( sqlsrv_stmt* stmt, bool& rows_present ); bool is_fixed_size_type( SQLINTEGER sql_type ); bool is_streamable_type( SQLINTEGER sql_type ); bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); @@ -204,15 +206,6 @@ PHP_FUNCTION( sqlsrv_free_stmt ) // this frees up the php resources as well remove_from_connection( stmt TSRMLS_CC ); - // cause any variables still holding a reference to this to be invalid so they cause - // an error when passed to a sqlsrv function. There's nothing we can do if the - // removal fails, so we just log it and move on. - int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_r )); - if( zr == FAILURE ) { - LOG( SEV_ERROR, LOG_STMT, "Failed to remove stmt resource %d", Z_RESVAL_P( stmt_r )); - } - ZVAL_NULL( stmt_r ); - RETURN_TRUE; } @@ -382,7 +375,7 @@ PHP_FUNCTION( sqlsrv_fetch_array ) // class property, a property with the result set field name is added to the // object and the result set value is applied to the property. For more // information about calling sqlsrv_fetch_object with the $className parameter, -// see How to: Retrieve Data as an Object (SQL Server 2005 Driver for PHP). +// see How to: Retrieve Data as an Object (SQL Server Driver for PHP). // // If a field with no name is returned, sqlsrv_fetch_object will discard the // field value and issue a warning. @@ -491,12 +484,20 @@ PHP_FUNCTION( sqlsrv_fetch_object ) fci.retval_ptr_ptr = &ctor_retval_z; fci.param_count = num_params; fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. +#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2 fci.object_pp = &return_value; +#else + fci.object_ptr = return_value; +#endif memset( &fcic, 0, sizeof( fcic )); fcic.initialized = 1; fcic.function_handler = (*class_entry)->constructor; fcic.calling_scope = *class_entry; +#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2 fcic.object_pp = &return_value; +#else + fcic.object_ptr = return_value; +#endif zr = zend_call_function( &fci, &fcic TSRMLS_CC ); if( zr == FAILURE ) { zval_ptr_dtor( &return_value ); @@ -655,7 +656,7 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // $fieldIndex: The index of the field to be retrieved. Indexes begin at zero. // $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines // the PHP data type for the returned data. For information about supported data -// types, see SQLSRV Constants (SQL Server 2005 Driver for PHP). If no return +// types, see SQLSRV Constants (SQL Server Driver for PHP). If no return // type is specified, a default PHP type will be returned. For information about // default PHP types, see Default PHP Data Types. For information about // specifying PHP data types, see How to: Specify PHP Data Types. @@ -753,6 +754,13 @@ PHP_FUNCTION( sqlsrv_next_result ) // call the ODBC API that does what we want r = SQLMoreResults( stmt->ctx.handle ); if( r == SQL_NO_DATA ) { + + if( stmt->param_output_strings ) { + if( !adjust_output_string_lengths( stmt, _FN_ TSRMLS_CC )) { + RETURN_FALSE; + } + } + // if we're at the end, then return NULL stmt->past_next_result_end = true; RETURN_NULL(); @@ -955,7 +963,6 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i { SQLRETURN r; SQLSMALLINT i; - emalloc_auto_ptr ind_ptr; close_active_stream( stmt TSRMLS_CC ); @@ -967,6 +974,8 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i } while( r != SQL_NO_DATA ); } + stmt->free_param_data(); + stmt->executed = false; if( stmt->params_z ) { @@ -974,7 +983,12 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i HashTable* params_ht = Z_ARRVAL_P( stmt->params_z ); - ind_ptr = static_cast( emalloc( zend_hash_num_elements( params_ht ) * sizeof( SQLINTEGER ))); + // allocate the buffer size array used by SQLBindParameter if it wasn't allocated by a + // previous execution. The size of the array cannot change because the number of parameters + // cannot change in between executions. + if( stmt->params_ind_ptr == NULL ) { + stmt->params_ind_ptr = static_cast( emalloc( zend_hash_num_elements( params_ht ) * sizeof( SQLINTEGER ))); + } for( i = 1, zend_hash_internal_pointer_reset( params_ht ); zend_hash_has_more_elements( params_ht ) == SUCCESS; @@ -1026,27 +1040,47 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); buffer = NULL; buffer_len = 0; - ind_ptr[ i-1 ] = SQL_NULL_DATA; + stmt->params_ind_ptr[ i-1 ] = SQL_NULL_DATA; break; case IS_LONG: buffer = ¶m_z->value; buffer_len = sizeof( param_z->value.lval ); - ind_ptr[ i-1 ] = buffer_len; + stmt->params_ind_ptr[ i-1 ] = buffer_len; break; case IS_DOUBLE: buffer = ¶m_z->value; buffer_len = sizeof( param_z->value.dval ); - ind_ptr[ i-1 ] = buffer_len; + stmt->params_ind_ptr[ i-1 ] = buffer_len; break; case IS_STRING: buffer = Z_STRVAL_PP( ¶m_z ); buffer_len = Z_STRLEN_PP( ¶m_z ); - ind_ptr[ i-1 ] = buffer_len; // resize the buffer to match the column size if it's smaller // than the buffer given already - if(( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT ) && buffer_len < column_size ) { + if(( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT )) { + if( stmt->param_output_strings == NULL ) { + ALLOC_INIT_ZVAL( stmt->param_output_strings ); + int zr = array_init( stmt->param_output_strings ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false ); + } + // if we don't have enough space, then reallocate the buffer (and copy its contents) + // the string keeps its original content until it is updated by the output, at which + // time its length will be set to match the output in adjust_output_string_parameters + if( buffer_len < column_size ) { buffer = static_cast( erealloc( buffer, column_size + 1 )); + buffer_len = column_size + 1; + reinterpret_cast( buffer )[ column_size ] = '\0'; + ZVAL_STRINGL( param_z, reinterpret_cast( buffer ), Z_STRLEN_P( param_z ), 0 ); + } + // register the output string so that it will be updated when adjust_output_string_parameters is called + sqlsrv_output_string output_string( param_z, i - 1 ); + HashTable* strings_ht = Z_ARRVAL_P( stmt->param_output_strings ); + int next_index = zend_hash_next_free_element( strings_ht ); + int zr = zend_hash_index_update( strings_ht, next_index, &output_string, sizeof( output_string ), NULL ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false ); + zval_add_ref( ¶m_z ); // we have a reference to the param in the statement } + stmt->params_ind_ptr[ i-1 ] = buffer_len; break; case IS_OBJECT: { @@ -1100,7 +1134,7 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false ); buffer_len = Z_STRLEN_P( buffer_z ); buffer_z.transferred(); - ind_ptr[ i-1 ] = buffer_len; + stmt->params_ind_ptr[ i-1 ] = buffer_len; break; } case IS_RESOURCE: @@ -1112,7 +1146,7 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i buffer = param_z; zval_add_ref( ¶m_z ); // so that it doesn't go away while we're using it buffer_len = 0; - ind_ptr[ i-1 ] = SQL_DATA_AT_EXEC; + stmt->params_ind_ptr[ i-1 ] = SQL_DATA_AT_EXEC; break; } default: @@ -1123,7 +1157,7 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i if( direction < 0 || direction > 0xffff ) DIE( "direction not valid SQLSMALLINT" ); r = SQLBindParameter( stmt->ctx.handle, i, static_cast( direction ), sql_c_type, sql_type.typeinfo.type, column_size, decimal_digits, - buffer, buffer_len, &ind_ptr[ i-1 ] ); + buffer, buffer_len, &stmt->params_ind_ptr[ i-1 ] ); CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); } @@ -1152,15 +1186,35 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i } } } - // if a result set was generated, check for errors. Otherwise, it completed successfully but no data was - // generated. - else if( r != SQL_NO_DATA ) { + // if no result set was generated just adjust any output string parameter lengths + else if( r == SQL_NO_DATA ) { + bool adjusted = adjust_output_string_lengths( stmt, _FN_ TSRMLS_CC ); + if( !adjusted ) { + return false; + } + } + else { + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // if we succeeded and are still here, then handle output string parameters + if( SQL_SUCCEEDED( r )) { + + bool rows_present; + r = has_rows( stmt, rows_present ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + if( !rows_present ) { + bool adjusted = adjust_output_string_lengths( stmt, _FN_ TSRMLS_CC ); + if( !adjusted ) { + return false; + } + } + } } - // false means to not release the datetime buffers we just allocated. - stmt->new_result_set( false ); + stmt->new_result_set(); stmt->executed = true; return true; @@ -1177,6 +1231,10 @@ void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC ) if( stmt->params_z ) { zval_ptr_dtor( &stmt->params_z ); stmt->params_z = NULL; + // free the parameter size buffer if we had one (if the statement was executed) + if( stmt->params_ind_ptr ) { + efree( stmt->params_ind_ptr ); + } } close_active_stream( stmt TSRMLS_CC ); @@ -1188,9 +1246,9 @@ void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC ) r = SQLFreeHandle( SQL_HANDLE_STMT, stmt->ctx.handle ); - // we don't check errors here because the error log may have already gone away. We just log them. + // we don't handle errors here because the error log may have already gone away. We just log them. if( !SQL_SUCCEEDED( r ) ) { - LOG( SEV_ERROR, LOG_STMT, "Failed to free handle for stmt resource %d", stmt->conn_index ); + LOG( SEV_ERROR, LOG_STMT, "Failed to free statement handle %1!d!", stmt->ctx.handle ); } // mark the statement as closed @@ -1207,16 +1265,22 @@ void free_php_resources( zval* stmt_z TSRMLS_DC ) return; } + stmt->free_param_data(); + // cause any variables still holding a reference to this to be invalid so // they cause an error when passed to a sqlsrv function. If the removal fails, // we log it. int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_z )); if( zr == FAILURE ) { - LOG( SEV_ERROR, LOG_STMT, "Failed to remove stmt resource %d", Z_RESVAL_P( stmt_z )); + LOG( SEV_ERROR, LOG_STMT, "Failed to remove stmt resource %1!d!", Z_RESVAL_P( stmt_z )); + } + else { + + // stmt won't leak if this isn't hit, since Zend cleans up the heap at the end of each request/script + efree( stmt ); } ZVAL_NULL( stmt_z ); - efree( stmt ); zval_ptr_dtor( &stmt_z ); } @@ -1241,7 +1305,7 @@ void sqlsrv_stmt_hash_dtor( void* stmt_ptr ) { zval* stmt_z = *(static_cast( stmt_ptr )); - if( stmt_z->refcount <= 0 ) { + if( Z_REFCOUNT_P( stmt_z ) <= 0 ) { DIE( "Statement refcount should be > 0 when deleting from the connection's statement list" ); } @@ -1277,10 +1341,27 @@ void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ) } } +// centralized place to release all the parameter data that accrues during the execution +// phase. +void sqlsrv_stmt::free_param_data( void ) +{ + // if we allocated any output string parameters in a previous execution, release them here. + if( param_output_strings ) { + zval_ptr_dtor( ¶m_output_strings ); + param_output_strings = NULL; + } + + // if we allocated any datetime strings in a previous execution, release them here. + if( param_datetime_buffers ) { + zval_ptr_dtor( ¶m_datetime_buffers ); + param_datetime_buffers = NULL; + } +} + // to be called whenever a new result set is created, such as after an // execute or next_result. Resets the state variables. -void sqlsrv_stmt::new_result_set( bool release_datetime_buffers ) +void sqlsrv_stmt::new_result_set( void ) { fetch_called = false; if( fetch_fields ) { @@ -1294,18 +1375,50 @@ void sqlsrv_stmt::new_result_set( bool release_datetime_buffers ) last_field_index = -1; past_fetch_end = false; past_next_result_end = false; - - // if we allocated any datetime strings, release them here. - if( param_datetime_buffers && release_datetime_buffers ) { - zval_ptr_dtor( ¶m_datetime_buffers ); - param_datetime_buffers = NULL; - } } // *** internal functions *** namespace { +SQLRETURN has_rows( sqlsrv_stmt* stmt, bool& rows_present ) +{ + // Use SQLNumResultCols to determine if we have rows or not. + SQLRETURN r; + SQLSMALLINT num_cols; + r = SQLNumResultCols( stmt->ctx.handle, &num_cols ); + rows_present = (num_cols != 0); + return r; +} + +// adjust_output_string_lengths +// called after all result sets are consumed or if there are no results sets, this function adjusts the length +// of any output string parameters to the length returned by ODBC in the ind_ptr buffer passed as to SQLBindParameter +bool adjust_output_string_lengths( sqlsrv_stmt* stmt, const char* _FN_ TSRMLS_DC ) +{ + if( stmt->param_output_strings == NULL ) + return true; + + HashTable* params_ht = Z_ARRVAL_P( stmt->param_output_strings ); + + for( zend_hash_internal_pointer_reset( params_ht ); + zend_hash_has_more_elements( params_ht ) == SUCCESS; + zend_hash_move_forward( params_ht ) ) { + + sqlsrv_output_string *output_string; + int zr = zend_hash_get_current_data( params_ht, (void**) &output_string ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, zval_ptr_dtor( &stmt->param_output_strings ); return false; ); + char* str = Z_STRVAL_P( output_string->string_z ); + int str_len = stmt->params_ind_ptr[ output_string->param_num ]; + ZVAL_STRINGL( output_string->string_z, str, str_len, 0 ); + } + + zval_ptr_dtor( &stmt->param_output_strings ); + stmt->param_output_strings = NULL; + + return true; +} + // check_for_next_stream_parameter // check for the next stream parameter. Returns true if another parameter is ready, false if either an error // or there are no more parameters. @@ -1575,7 +1688,7 @@ sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value else { sql_type.typeinfo.type = SQL_VARBINARY; } - if( Z_STRLEN_P( value ) > SQL_SERVER_2005_MAX_FIELD_SIZE ) { + if( Z_STRLEN_P( value ) > SQL_SERVER_MAX_FIELD_SIZE ) { sql_type.typeinfo.size = SQLSRV_SIZE_MAX_TYPE; } else { @@ -1722,7 +1835,7 @@ bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, __out SQLUI if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { *column_size = SQL_SS_LENGTH_UNLIMITED; } - else if( *column_size > SQL_SERVER_2005_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { + else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { *column_size = SQLSRV_INVALID_SIZE; return false; } @@ -1980,7 +2093,7 @@ void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; ); } } - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_2005_MAX_FIELD_SIZE ) { + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { // only allow binary retrievals for char and binary types. All others get a char type automatically. if( is_fixed_size_type( sql_type )) { c_type = SQL_C_CHAR; @@ -2047,7 +2160,7 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) } } - int max_size = SQL_SERVER_2005_MAX_FIELD_SIZE; + int max_size = SQL_SERVER_MAX_FIELD_SIZE; // size is actually the number of characters, not the number of bytes, so if they ask for a // 2 byte per character type, then we half the maximum size allowed. if( type == SQL_WVARCHAR || type == SQL_WCHAR ) { @@ -2076,15 +2189,15 @@ void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) SQLSRV_UNUSED( this_ptr ); SQLSRV_UNUSED( return_value_ptr ); - long prec = SQL_SERVER_2005_DEFAULT_PRECISION; - long scale = SQL_SERVER_2005_DEFAULT_SCALE; + long prec = SQL_SERVER_DEFAULT_PRECISION; + long scale = SQL_SERVER_DEFAULT_SCALE; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { return; } - if( prec > SQL_SERVER_2005_MAX_PRECISION ) { + if( prec > SQL_SERVER_MAX_PRECISION ) { LOG( SEV_ERROR, LOG_STMT, "Invalid precision. Precision can't be > 38" ); prec = SQLSRV_INVALID_PRECISION; } @@ -2511,7 +2624,7 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval* if( Z_TYPE_PP( var_or_val ) == IS_NULL ) { Z_TYPE_PP( var_or_val ) = php_type; if( php_type == IS_STRING ) { - Z_STRVAL_PP( var_or_val ) = static_cast( emalloc( column_size )); + ZVAL_STRINGL( *var_or_val, static_cast( emalloc( column_size )), column_size, 0 /* don't dup the string */ ); } } else { diff --git a/stream.cpp b/stream.cpp index 0f8fa5d6..8367f711 100644 --- a/stream.cpp +++ b/stream.cpp @@ -126,7 +126,7 @@ OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "STREAMS_DC is a Zend macro that evals // open a stream and return the sqlsrv_stream_ops function table as part of the // return value. There is only one valid way to open a stream, using sqlsrv_get_field on // certain field types. A sqlsrv stream may only be opened in read mode. -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, +php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in char*, __in char* mode, int options, __in char **, php_stream_context* diff --git a/template.rc b/template.rc index 50b99658..ea0710aa 100644 --- a/template.rc +++ b/template.rc @@ -58,9 +58,9 @@ BEGIN VALUE "FileDescription", FILE_DESCRIPTION "\0" VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) VALUE "InternalName", FILE_NAME "\0" - VALUE "LegalCopyright", "© 2008 Microsoft Corp. All Rights Reserved.\0" + VALUE "LegalCopyright", "© 2009 Microsoft Corp. All Rights Reserved.\0" VALUE "OriginalFilename", FILE_NAME "\0" - VALUE "ProductName", "Microsoft SQL Server 2005 Driver for PHP\0" + VALUE "ProductName", "Microsoft SQL Server Driver for PHP\0" VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD) VALUE "URL", "http://www.microsoft.com\0" END diff --git a/util.cpp b/util.cpp index 8d161cdc..9e073fcc 100644 --- a/util.cpp +++ b/util.cpp @@ -3,7 +3,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. // -// Contents: Utility functions for the SQL Server 2005 Driver for PHP 1.0 +// Contents: Utility functions for the SQL Server Driver for PHP 1.0 // // Comments: Mostly error handling and some type handling // @@ -165,6 +165,10 @@ sqlsrv_error SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF[] = { sqlsrv_error SQLSRV_ERROR_REGISTER_RESOURCE[] = { { IMSSP, "Registering the %1!s! resource failed.", -39, true } }; +sqlsrv_error SQLSRV_ERROR_DRIVER_NOT_INSTALLED[] = { + { IMSSP, "The SQL Server Driver for PHP requires the SQL Server 2005 Native Client ODBC Driver to communicate with SQL Server. " + "That ODBC Driver is not currently installed. Accessing the following URL will download the SQL Server 2005 Native Client ODBC driver for %1!s!: %2!s!", -40, true } +}; // internal warning definitions @@ -173,6 +177,11 @@ sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[] = { }; +// This warning is special since it's reported by php_error rather than sqlsrv_errors. That's also why it has +// a printf format specification instead of a FormatMessage format specification. +sqlsrv_error PHP_WARNING_VAR_NOT_REFERENCE[] = { + { SSPWARN, "Variable parameter %d not passed by reference (prefaced with an &). Variable parameters passed to sqlsrv_prepare should be passed by reference, not by value. For more information, see sqlsrv_prepare in the API Reference section of the product documentation.", -101, true } +}; // sqlsrv_errors( [int $errorsAndOrWarnings] ) // @@ -237,7 +246,7 @@ PHP_FUNCTION( sqlsrv_errors ) zval_ptr_dtor( &both_z ); RETURN_FALSE; } - both_z->is_ref = 1; + Z_SET_ISREF_P( both_z ); if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( errors ) TSRMLS_CC )) { zend_hash_destroy( Z_ARRVAL_P( both_z )); RETURN_FALSE; diff --git a/version.h b/version.h index 63678457..17390f28 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. // -// Contents: Routines that use connection handles +// Contents: Version resource information // // Comments: // @@ -11,9 +11,9 @@ // may be found online at http://www.codeplex.com/SQL2K5PHP/license. //---------------------------------------------------------------------------------------------------------------------------------- -#define VER_FILEVERSION_STR "1.0.1015.0" -#define _FILEVERSION 1,0,1015,0 +#define VER_FILEVERSION_STR "1.0.0.0" +#define _FILEVERSION 1,0,0,0 #define SQLVERSION_MAJOR 1 #define SQLVERSION_MINOR 0 -#define SQLVERSION_MMDD 1015 +#define SQLVERSION_MMDD 0 #define SQLVERSION_REVISION 0