SQL Server Driver for PHP 1.1 October 2009

This commit is contained in:
vineetc 2009-10-07 23:40:27 +00:00
parent 273ea337e7
commit 08be0b80ff
9 changed files with 220 additions and 144 deletions

View file

@ -1 +1 @@
SQL Server Driver for PHP 1.1 August 2009 CTP
SQL Server Driver for PHP 1.1 October 2009

View file

@ -1,11 +1,11 @@
* Notes about changes to the SQL Server Driver for PHP 1.1 August 2009 CTP *
* Notes about changes to the SQL Server Driver for PHP 1.1 October 2009 *
For details about the changes included in this release, 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 Driver for PHP 1.1 August 2009 CTP *
* Notes about compiling the SQL Server Driver for PHP 1.1 October 2009 *
Prerequisites: You must first be able to build PHP without including
the SQL Server Driver for PHP extension. For help with doing this, see
@ -32,7 +32,7 @@ wish to do so, run "nmake clean" first.
5) To install the resulting build, run "nmake install" or just copy
php_sqlsrv.dll to your PHP extension directory.
This software has been compiled and tested under PHP 5.2.10 and PHP 5.3.0
This software has been compiled and tested under PHP 5.2.11 and PHP 5.3.0
using the Visual C++ 2008 Express and Standard compilers.
This software is released under the Microsoft Public License. A copy of

View file

@ -43,22 +43,15 @@ 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" }
};
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
// *** 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* conn, const char* server, zval const* options,
__inout std::string& connection_string TSRMLS_DC );
SQLRETURN determine_server_version( sqlsrv_conn* conn, const char* _FN_ TSRMLS_DC );
struct _platform_url* get_driver_info( void );
const char* get_processor_arch( void );
bool mark_params_by_reference( zval** params_zz, char const* _FN_ TSRMLS_DC );
void sqlsrv_conn_close_stmts( sqlsrv_conn* conn TSRMLS_DC );
@ -201,20 +194,20 @@ PHP_FUNCTION( sqlsrv_connect )
catch( std::bad_alloc& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %s", ex.what() );
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %1!s!", ex.what() );
LOG( SEV_ERROR, LOG_CONN, "C++ memory allocation failure building the connection string." );
DIE( "C++ memory allocation failure building the connection string." );
}
catch( std::out_of_range const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %s", ex.what() );
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %1!s!", ex.what() );
RETURN_FALSE;
}
catch( std::length_error const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %s", ex.what() );
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %1!s!", ex.what() );
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = SQL_NULL_HANDLE;
RETURN_FALSE;
@ -246,8 +239,8 @@ PHP_FUNCTION( sqlsrv_connect )
SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// if it's a IM002, meaning 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 );
const char* arch = get_processor_arch();
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_DRIVER_NOT_INSTALLED TSRMLS_CC, arch );
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = SQL_NULL_HANDLE;
RETURN_FALSE;
@ -461,7 +454,7 @@ PHP_FUNCTION( sqlsrv_close )
// removal fails, so we just log it and move on.
int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( conn_r ));
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_CONN, "Failed to remove connection resource %d", Z_RESVAL_P( conn_r ));
LOG( SEV_ERROR, LOG_CONN, "Failed to remove connection resource %1!d!", Z_RESVAL_P( conn_r ));
}
ZVAL_NULL( conn_r );
@ -1689,20 +1682,21 @@ bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS
return true;
}
struct _platform_url* get_driver_info( void )
const char* get_processor_arch( void )
{
SYSTEM_INFO sys_info;
GetSystemInfo( &sys_info );
GetSystemInfo( &sys_info);
switch( sys_info.wProcessorArchitecture ) {
case PROCESSOR_ARCHITECTURE_INTEL:
return &driver_info[0];
return PROCESSOR_ARCH[0];
case PROCESSOR_ARCHITECTURE_AMD64:
return &driver_info[1];
return PROCESSOR_ARCH[1];
case PROCESSOR_ARCHITECTURE_IA64:
return &driver_info[2];
return PROCESSOR_ARCH[2];
default:
DIE( "Unknown Windows processor architecture" );
return NULL;
@ -1729,21 +1723,16 @@ SQLRETURN determine_server_version( sqlsrv_conn* conn, const char* _FN_ TSRMLS_D
version_major_str[ 2 ] = '\0';
version_major = static_cast<SERVER_VERSION>( atoi( version_major_str ));
if( errno == ERANGE || errno == EINVAL ) {
if( version_major == 0 || errno == ERANGE || errno == EINVAL ) {
conn->server_version = SERVER_VERSION_UNKNOWN;
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_SERVER_VERSION TSRMLS_CC, version_major );
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION TSRMLS_CC );
return SQL_ERROR;
}
if( version_major >= SERVER_VERSION_2000 ) {
conn->server_version = version_major;
return SQL_SUCCESS;
}
else {
conn->server_version = SERVER_VERSION_UNKNOWN;
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_SERVER_VERSION TSRMLS_CC, version_major );
return SQL_ERROR;
}
// SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least
// that high
conn->server_version = version_major;
return SQL_SUCCESS;
}
} // namespace

View file

@ -209,6 +209,7 @@ zend_function_entry sqlsrv_functions[] = {
HMODULE g_sqlsrv_hmodule = NULL;
HENV g_henv_ncp = SQL_NULL_HANDLE;
HENV g_henv_cp = SQL_NULL_HANDLE;
OSVERSIONINFO g_osversion;
// the structure returned to Zend that exposes the extension to the Zend engine.
@ -471,6 +472,15 @@ PHP_MINIT_FUNCTION(sqlsrv)
}
CHECK_SQL_WARNING( r, (&henv_ctx), _FN_, NULL );
// get the version of the OS we're running on. For now this governs certain flags used by
// WideCharToMultiByte. It might be relevant to other things in the future.
g_osversion.dwOSVersionInfoSize = sizeof( g_osversion );
BOOL ver_return = GetVersionEx( &g_osversion );
if( !ver_return ) {
LOG( SEV_ERROR, LOG_INIT, "Failed to retrieve Windows version information." );
return FAILURE;
}
return SUCCESS;
}
@ -542,7 +552,7 @@ PHP_RINIT_FUNCTION(sqlsrv)
// initialize list of warnings to ignore
ALLOC_HASHTABLE( SQLSRV_G( warnings_to_ignore ));
int zr = zend_hash_init( SQLSRV_G( warnings_to_ignore ), 5, NULL, NULL, 0 );
int zr = zend_hash_init( SQLSRV_G( warnings_to_ignore ), 6, NULL, NULL, 0 );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
@ -558,6 +568,7 @@ PHP_RINIT_FUNCTION(sqlsrv)
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// changed language warning
to_ignore.sqlstate = "01000";
to_ignore.native_message = NULL;
@ -568,6 +579,7 @@ PHP_RINIT_FUNCTION(sqlsrv)
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// option value changed
to_ignore.sqlstate = "01S02";
to_ignore.native_message = NULL;
@ -578,6 +590,7 @@ PHP_RINIT_FUNCTION(sqlsrv)
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// cursor operation conflict
to_ignore.sqlstate = "01001";
to_ignore.native_message = NULL;
@ -588,7 +601,8 @@ PHP_RINIT_FUNCTION(sqlsrv)
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// null value eliminiated in set function
// null value eliminated in set function
to_ignore.sqlstate = "01003";
to_ignore.native_message = NULL;
to_ignore.native_code = -1;
@ -598,6 +612,17 @@ PHP_RINIT_FUNCTION(sqlsrv)
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// SQL Azure warning: This session has been assigned a tracing id of ..
to_ignore.sqlstate = "01000";
to_ignore.native_message = NULL;
to_ignore.native_code = 40608;
to_ignore.format = false;
zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// supported encodings
ALLOC_HASHTABLE( SQLSRV_G( encodings ));
@ -652,6 +677,10 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv)
zend_hash_destroy( SQLSRV_G( warnings_to_ignore ));
FREE_HASHTABLE( SQLSRV_G( warnings_to_ignore ));
}
if( SQLSRV_G( encodings )) {
zend_hash_destroy( SQLSRV_G( encodings ));
FREE_HASHTABLE( SQLSRV_G( encodings ));
}
sqlsrv_free( SQLSRV_G( henv_context ));
// verify memory at the end of the request (in debug mode only)

View file

@ -80,6 +80,11 @@ OACR_WARNING_POP
#include <sql.h>
#include <sqlext.h>
#if !defined(WC_ERR_INVALID_CHARS)
// imported from winnls.h as it isn't included by 5.3.0
#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars
#endif
// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when
// we use std::copy, which causes compilation to fail since we compile with warnings as errors.
#if defined(ZEND_DEBUG) && defined(inline)
@ -100,6 +105,8 @@ OACR_WARNING_POP
#define SQL_SS_TIME2 (-154)
#define SQL_SS_TIMESTAMPOFFSET (-155)
// static assert for enforcing compile time conditions
template <bool b>
struct sqlsrv_static_assert;
@ -234,6 +241,9 @@ extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to
extern HMODULE g_sqlsrv_hmodule; // used for getting the version information
extern SQLHANDLE g_henv_ncp; // used to create connection handles with connection pooling off
extern SQLHANDLE g_henv_cp; // used to create connection handles with connection pooling on
extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in
const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista
// maps an IANA encoding to a code page
struct sqlsrv_encoding {
@ -332,9 +342,10 @@ struct sqlsrv_output_string {
zval* string_z;
unsigned int encoding;
int param_num; // used to index into the ind_or_len of the statement
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
sqlsrv_output_string( zval* str_z, unsigned int enc, int num ) :
string_z( str_z ), encoding( enc ), param_num( num )
sqlsrv_output_string( zval* str_z, unsigned int enc, int num, SQLUINTEGER buffer_len ) :
string_z( str_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len )
{
}
@ -362,7 +373,7 @@ struct sqlsrv_stmt {
bool past_fetch_end; // sqlsrv_fetch sets when the statement goes beyond the last row
bool past_next_result_end; // sqlsrv_next_resultset sets when the statement goes beyond the last results
zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute
SQLINTEGER* params_ind_ptr; // buffer to hold the sizes returend by ODBC
SQLLEN* params_ind_ptr; // buffer to hold the sizes returned by ODBC
zval* param_datetime_buffers; // track which datetime parameter to convert from string to datetime objects
zval* param_streams; // track which streams to send data to the server
zval* param_strings; // track which output strings need to be converted to UTF-8
@ -482,6 +493,7 @@ unsigned int log_subsystems;
zend_bool warnings_return_as_errors;
// special list of warnings to ignore even if warnings are treated as errors
HashTable* warnings_to_ignore;
// encodings we understand
HashTable* encodings;
ZEND_END_MODULE_GLOBALS(sqlsrv)
@ -672,7 +684,7 @@ extern sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE[];
extern sqlsrv_error SQLSRV_ERROR_STATEMENT_SCROLLABLE[];
extern sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_STYLE[];
extern sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE[];
extern sqlsrv_error SQLSRV_ERROR_INVALID_SERVER_VERSION[];
extern sqlsrv_error SQLSRV_ERROR_UNKNOWN_SERVER_VERSION[];
// definitions for PHP specific warnings returned by sqlsrv
extern sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[];

216
stmt.cpp
View file

@ -115,7 +115,7 @@ bool calc_string_size( sqlsrv_stmt const* s, SQLUSMALLINT field_index, SQLUINTEG
bool check_for_next_stream_parameter( sqlsrv_stmt* stmt, zval* return_value, const char* _FN_ TSRMLS_DC );
void close_active_stream( sqlsrv_stmt* s TSRMLS_DC );
bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z );
bool convert_string_from_utf16( sqlsrv_phptype sqlsrv_phptype, char** string, SQLINTEGER& len );
bool convert_string_from_utf16( unsigned int encoding, char** string, SQLINTEGER& len );
SQLSMALLINT determine_c_type( int php_type, int encoding );
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, SQLUINTEGER* column_size, SQLSMALLINT* decimal_digits );
bool determine_param_defaults( sqlsrv_stmt const* stmt, const char* _FN_, zval const* param_z, int param_num, zend_uchar& php_type, int& direction,
@ -1242,7 +1242,7 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
SQLUINTEGER column_size = 0;
SQLSMALLINT decimal_digits = 0;
SQLPOINTER buffer = NULL;
SQLUINTEGER buffer_len = 0;
SQLLEN buffer_len = 0;
sqlsrv_sqltype sql_type;
sqlsrv_phptype sqlsrv_phptype;
sql_type.typeinfo.type = SQL_BINARY;
@ -1302,6 +1302,7 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
case IS_STRING:
buffer = Z_STRVAL_PP( &param_z );
buffer_len = Z_STRLEN_PP( &param_z );
stmt->params_ind_ptr[ i-1 ] = buffer_len;
if( direction == SQL_PARAM_INPUT && sqlsrv_phptype.typeinfo.encoding == CP_UTF8 ) {
zval_auto_ptr wbuffer_z;
@ -1317,68 +1318,96 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
// memory added here is released upon function exit
add_next_index_zval( wbuffer_allocs, wbuffer_z );
wbuffer_z.transferred();
stmt->params_ind_ptr[ i-1 ] = buffer_len;
}
// if the output params zval in the statement isn't initialized, do so
if( direction != SQL_PARAM_INPUT && stmt->param_strings == NULL ) {
ALLOC_INIT_ZVAL( stmt->param_strings );
int zr = array_init( stmt->param_strings );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false );
}
if( direction == SQL_PARAM_OUTPUT ) {
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to use it.
if( buffer_len < column_size ) {
buffer = static_cast<char*>( sqlsrv_realloc( buffer, column_size + 1 ));
buffer_len = column_size + 1;
ZVAL_STRINGL( param_z, reinterpret_cast<char*>( buffer ), buffer_len, 0 );
// if it is an output or input/output string parameters
if( direction != SQL_PARAM_INPUT ) {
// lazy allocate the array that holds the output string parameters. These are held so that their lengths
// may be adjusted after output strings are fully retrieved.
if( stmt->param_strings == NULL ) {
ALLOC_INIT_ZVAL( stmt->param_strings );
int zr = array_init( stmt->param_strings );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false );
}
// save the parameter to be adjusted and/or converted after the results are processed
sqlsrv_output_string output_string( param_z, sqlsrv_phptype.typeinfo.encoding, i - 1 );
HashTable* strings_ht = Z_ARRVAL_P( stmt->param_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( &param_z ); // we have a reference to the param
}
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
buffer = Z_STRVAL_PP( &param_z );
buffer_len = Z_STRLEN_PP( &param_z );
if( sqlsrv_phptype.typeinfo.encoding == CP_UTF8 ) {
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
// convert it to wchar first
if( direction == SQL_PARAM_INPUT_OUTPUT &&
(sql_c_type == SQL_C_WCHAR ||
(sql_c_type == SQL_C_BINARY &&
(sql_type.typeinfo.type == SQL_WCHAR ||
sql_type.typeinfo.type == SQL_WVARCHAR ||
sql_type.typeinfo.type == SQL_WLONGVARCHAR )))) {
bool converted = convert_input_param_to_utf16( param_z, param_z );
if( !converted ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE TSRMLS_CC, i, get_last_error_message() );
return false;
}
// free the original buffer and set to our converted buffer
sqlsrv_free( buffer );
buffer = Z_STRVAL_PP( &param_z );
buffer_len = Z_STRLEN_PP( &param_z );
// save the parameter to be adjusted and/or converted after the results are processed
sqlsrv_output_string output_string( param_z, sqlsrv_phptype.typeinfo.encoding, i - 1 );
HashTable* strings_ht = Z_ARRVAL_P( stmt->param_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( &param_z );
}
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to use it.
if( buffer_len < column_size ) {
buffer = static_cast<char*>( sqlsrv_realloc( buffer, column_size + 1 ));
buffer_len = column_size;
ZVAL_STRINGL( param_z, reinterpret_cast<char*>( buffer ), buffer_len, 0 );
efree( buffer );
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
}
// calculate the necessary size for the output buffer and reallocate the buffer given if necessary.
// verify there is enough buffer space to hold the output parameter and set variables for the
// correct sizes
SQLLEN expected_len;
SQLLEN buffer_null_extra;
SQLLEN elem_size;
SQLLEN without_null_len;
// calculate the size of each 'element' represented by column_size. WCHAR is of course 2,
// as is a n(var)char/ntext field being returned as a binary field.
elem_size = (sql_c_type == SQL_C_WCHAR || (sql_c_type == SQL_C_BINARY && (sql_type.typeinfo.type == SQL_WCHAR || sql_type.typeinfo.type == SQL_WVARCHAR))) ? 2 : 1;
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
expected_len = column_size * elem_size + elem_size;
// binary fields aren't null terminated, so we need to account for that in our buffer length calcuations
buffer_null_extra = (sql_c_type == SQL_C_BINARY) ? elem_size : 0;
// this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter
without_null_len = column_size * elem_size;
// increment to include the null terminator
buffer_len += elem_size;
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new
// length.
if( buffer_len < expected_len ) {
if( expected_len < expected_len - buffer_null_extra ) {
DIE( "Integer overflow/underflow caused a corrupt field length." );
return false; // avoid a static analysis error
}
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
// not having a NULL terminator on a string.
buffer = static_cast<char*>( sqlsrv_realloc( buffer, expected_len ));
buffer_len = expected_len; // set the buffer_len to the new allocation size (includes the null terminator taken out below)
// A zval string len doesn't include the null. This calculates the length it should be
// regardless of whether the ODBC type contains the NULL or not.
ZVAL_STRINGL( param_z, reinterpret_cast<char*>( buffer ), without_null_len, 0 );
// null terminate the string to avoid a warning in debug PHP builds
(static_cast<char*>(buffer))[ without_null_len ] = '\0';
}
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
buffer_len -= buffer_null_extra;
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the buffer minus
// any NULL needed. This should never be less than the output length required. If it is more,
// the error 22001 is returned by ODBC.
stmt->params_ind_ptr[ i-1 ] = buffer_len - (elem_size - buffer_null_extra);
// save the parameter to be adjusted and/or converted after the results are processed
sqlsrv_output_string output_string( param_z, sqlsrv_phptype.typeinfo.encoding, i - 1, buffer_len );
HashTable* strings_ht = Z_ARRVAL_P( stmt->param_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( &param_z ); // we have a reference to the param
}
// correct the column size to be number of characters, not the number of bytes.
if( sql_type.typeinfo.type == SQL_WCHAR || sql_type.typeinfo.type == SQL_WVARCHAR ) {
column_size /= sizeof( wchar_t );
}
stmt->params_ind_ptr[ i-1 ] = buffer_len;
break;
case IS_OBJECT:
{
char* class_name;
sqlsrv_malloc_auto_ptr<char> class_name;
zend_uint class_name_len;
zval_auto_ptr function_z;
zval_auto_ptr buffer_z;
@ -1705,43 +1734,28 @@ bool adjust_output_lengths_and_encodings( sqlsrv_stmt* stmt, const char* _FN_ TS
// adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter
char* str = Z_STRVAL_P( output_string->string_z );
int str_len = stmt->params_ind_ptr[ output_string->param_num ];
SQLLEN str_len = stmt->params_ind_ptr[ output_string->param_num ];
if( str_len == SQL_NULL_DATA ) {
ZVAL_NULL( output_string->string_z );
continue;
}
// if it's not in the 8 bit encodings, then it's in UTF-16
if( output_string->encoding != SQLSRV_ENCODING_CHAR && output_string->encoding != SQLSRV_ENCODING_BINARY ) {
str_len >>= 1; // from # of bytes to # of wchars
++str_len; // include the NULL character
// get the size of the wide char string
int enc_size = WideCharToMultiByte( output_string->encoding, 0, reinterpret_cast<wchar_t*>( str ), str_len, NULL, 0, NULL, NULL );
// if no errors occurred
if( enc_size != 0 ) {
// allocate a buffer large enough
char* enc_buffer = reinterpret_cast<char*>( sqlsrv_malloc( enc_size + 1 ));
// convert the string
int r = WideCharToMultiByte( CP_UTF8, 0, reinterpret_cast<wchar_t*>( str ), str_len, enc_buffer, enc_size, NULL, NULL );
// if an error occurred during conversion
if( r == 0 ) {
// free the UTF-8 string and leave the current output param alone
sqlsrv_free( enc_buffer );
converted = false;
}
else {
--enc_size;
// swap the converted string for the original string
enc_buffer[ enc_size ] = '\0';
ZVAL_STRINGL( output_string->string_z, enc_buffer, enc_size, 0 );
sqlsrv_free( str );
}
}
else {
converted = false;
bool converted = convert_string_from_utf16( output_string->encoding, &str, str_len );
if( !converted ) {
return false;
}
}
else {
ZVAL_STRINGL( output_string->string_z, str, str_len, 0 );
str[ str_len ] = '\0'; // null terminate the string to avoid the debug php warning
else if( output_string->encoding == SQLSRV_ENCODING_BINARY && str_len < output_string->original_buffer_len ) {
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
// so we do that here if the length of the returned data is less than the original allocation. The original
// allocation null terminates the buffer already.
str[ str_len ] = '\0';
}
// set the string length
ZVAL_STRINGL( output_string->string_z, str, str_len, 0 );
}
zval_ptr_dtor( &stmt->param_strings );
@ -2197,7 +2211,6 @@ sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sq
// put in the column size and scale/decimal digits of the sql server type
// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx
// column_size is actually the size of the field in bytes, so a nvarchar(4000) is 8000 bytes
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, __out SQLUINTEGER* column_size, __out SQLSMALLINT* decimal_digits )
{
*decimal_digits = 0;
@ -2257,7 +2270,6 @@ bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
}
*column_size *= 2; // convert to byte size from wchar size
if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
@ -2489,20 +2501,33 @@ void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC )
}
}
// convert a string from utf-16 to another encoding and return the new string. The utf-16 string is released
// by this function if no errors occurred. Otherwise the parameters are not changed.
// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new
// length in the len parameter. If no errors occurred during convertion, true is returned and the original
// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed
// and false is returned.
bool convert_string_from_utf16( sqlsrv_phptype sqlsrv_phptype, char** string, SQLINTEGER& len )
bool convert_string_from_utf16( unsigned int encoding, char** string, SQLINTEGER& len )
{
char* utf16_string = *string;
unsigned int utf16_len = len / 2; // from # of bytes to # of wchars
char *enc_string = NULL;
unsigned int enc_len = 0;
++utf16_len; // include the NULL character
// for the empty string, we simply returned we converted it
if( len == 0 && *string[0] == '\0' ) {
return true;
}
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
DWORD flags = 0;
if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
// calculate the number of characters needed
enc_len = WideCharToMultiByte( sqlsrv_phptype.typeinfo.encoding, 0,
enc_len = WideCharToMultiByte( encoding, flags,
reinterpret_cast<LPCWSTR>( utf16_string ), utf16_len,
NULL, 0, NULL, NULL );
if( enc_len == 0 ) {
@ -2510,17 +2535,18 @@ bool convert_string_from_utf16( sqlsrv_phptype sqlsrv_phptype, char** string, SQ
}
// we must allocate a new buffer because it is possible that a UTF-8 string is longer than
// the corresponding UTF-16 string, so we cannot use an inplace conversion
enc_string = reinterpret_cast<char*>( sqlsrv_malloc( enc_len + 1 ));
int rc = WideCharToMultiByte( sqlsrv_phptype.typeinfo.encoding, 0,
enc_string = reinterpret_cast<char*>( sqlsrv_malloc( enc_len + 1 /* NULL char*/ ));
int rc = WideCharToMultiByte( encoding, flags,
reinterpret_cast<LPCWSTR>( utf16_string ), utf16_len,
enc_string, enc_len, NULL, NULL );
if( rc == 0 ) {
return false;
}
enc_string[ enc_len ] = '\0'; // null terminate the encoded string
sqlsrv_free( utf16_string );
*string = enc_string;
len = enc_len - 1;
len = enc_len;
return true;
}
@ -2699,7 +2725,7 @@ void get_field_as_string( sqlsrv_stmt const* s, sqlsrv_phptype sqlsrv_phptype, S
}
if( c_type == SQL_C_WCHAR ) {
bool converted = convert_string_from_utf16( sqlsrv_phptype, &field, field_len );
bool converted = convert_string_from_utf16( sqlsrv_phptype.typeinfo.encoding, &field, field_len );
if( !converted ) {
handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
sqlsrv_free( field );
@ -2739,7 +2765,7 @@ void get_field_as_string( sqlsrv_stmt const* s, sqlsrv_phptype sqlsrv_phptype, S
if( c_type == SQL_C_WCHAR ) {
bool converted = convert_string_from_utf16( sqlsrv_phptype, &field, field_len );
bool converted = convert_string_from_utf16( sqlsrv_phptype.typeinfo.encoding, &field, field_len );
if( !converted ) {
handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
sqlsrv_free( field );
@ -2995,8 +3021,8 @@ void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, long fetch_style,
SQLLEN field_type;
SQLLEN field_len;
sqlsrv_phptype sqlsrv_php_type;
// we don't use a zend_auto_ptr because ownership is transferred to the fields hash table
// that will be destroyed if an error occurs.
// this zval_auto_ptr is never transferred because we rely on its destructor to decrement the reference count
// we increment its reference count within each fetch type (below)
zval_auto_ptr field;
MAKE_STD_ZVAL( field );

View file

@ -101,8 +101,16 @@ size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, si
// convert it to UTF-8 if that's what's needed
if( c_type == SQL_C_WCHAR ) {
count *= 2; // undo the shift to use the full buffer
int w = WideCharToMultiByte( ss->encoding, 0, reinterpret_cast<LPCWSTR>( ss->stmt->param_buffer ),
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
DWORD flags = 0;
if( ss->encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
int w = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast<LPCWSTR>( ss->stmt->param_buffer ),
read >> 1, buf, count, NULL, NULL );
if( w == 0 ) {
stream->eof = 1;

View file

@ -8,7 +8,7 @@
// Comments:
//
// License: This software is released under the Microsoft Public License. A copy of the license agreement
// may be found online at http://www.codeplex.com/SQL2K5PHP/license.
// may be found online at http://www.codeplex.com/SQLSRVPHP/license.
//----------------------------------------------------------------------------------------------------------------------------------
#ifdef APSTUDIO_INVOKED

View file

@ -195,7 +195,7 @@ sqlsrv_error SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING[] = {
};
sqlsrv_error SQLSRV_ERROR_DRIVER_NOT_INSTALLED[] = {
{ IMSSP, "The SQL Server Driver for PHP requires the SQL Server 2008 Native Client ODBC Driver (SP1 or later) to communicate with SQL Server. "
"That ODBC Driver is not currently installed. Accessing the following URL will download the SQL Server 2008 Native Client ODBC driver for %1!s!: %2!s!", -49, true }
"That ODBC Driver is not currently installed. Access the following URL to download the SQL Server 2008 Native Client ODBC driver for %1!s!: http://go.microsoft.com/fwlink/?LinkId=163712", -49, true }
};
sqlsrv_error SQLSRV_ERROR_MARS_OFF[] = {
{ IMSSP, "The connection cannot process this operation because there is a statement with pending results. "
@ -214,8 +214,8 @@ sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_STYLE[] = {
sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE[] = {
{ IMSSP, "The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', 'keyset', or 'forward'.", -54, false }
};
sqlsrv_error SQLSRV_ERROR_INVALID_SERVER_VERSION[] = {
{ IMSSP, "Attempted to connect to a server of version %1!d!. Only connections to SQL Server 2000 (8) or later are supported.", -55, true }
sqlsrv_error SQLSRV_ERROR_UNKNOWN_SERVER_VERSION[] = {
{ IMSSP, "Failed to retrieve the server version. Unable to continue.", -55, false }
};
@ -784,6 +784,8 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
zval_ptr_dtor( &ssphp_z );
break;
}
// increasing the ref count since we've added it to the ssphp_z array twice
zval_add_ref( &temp );
MAKE_STD_ZVAL( temp );
ZVAL_LONG( temp, ssphp->native_code );
zr = add_next_index_zval( ssphp_z, temp );
@ -796,6 +798,8 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
zval_ptr_dtor( &ssphp_z );
break;
}
// increasing the ref count since we've added it to the ssphp_z array twice
zval_add_ref( &temp );
MAKE_STD_ZVAL( temp );
ZVAL_STRING( temp, const_cast<char*>( ssphp->native_message ), 1 );
zr = add_next_index_zval( ssphp_z, temp );
@ -808,6 +812,8 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
zval_ptr_dtor( &ssphp_z );
break;
}
// increasing the ref count since we've added it to the ssphp_z array twice
zval_add_ref( &temp );
if( ignore_warning( ssphp->sqlstate, ssphp->native_code TSRMLS_CC ) && ignored_chain != NULL ) {
zr = add_next_index_zval( *ignored_chain, ssphp_z );
@ -861,6 +867,8 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
zval_ptr_dtor( &ssphp_z );
continue;
}
// increasing the ref count since we've added it to the ssphp_z array twice
zval_add_ref( &temp );
MAKE_STD_ZVAL( temp );
ZVAL_LONG( temp, native_error );
zr = add_next_index_zval( ssphp_z, temp );
@ -873,6 +881,8 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
zval_ptr_dtor( &ssphp_z );
continue;
}
// increasing the ref count since we've added it to the ssphp_z array twice
zval_add_ref( &temp );
MAKE_STD_ZVAL( temp );
ZVAL_STRINGL( temp, reinterpret_cast<char*>( message_text ), message_len, 1 );
zr = add_next_index_zval( ssphp_z, temp );
@ -885,6 +895,8 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
zval_ptr_dtor( &ssphp_z );
continue;
}
// increasing the ref count since we've added it to the ssphp_z array twice
zval_add_ref( &temp );
if( ignore_warning( reinterpret_cast<const char*>( sql_state ), native_error TSRMLS_CC ) && ignored_chain != NULL ) {
zr = add_next_index_zval( *ignored_chain, ssphp_z );