SQL Server Driver for PHP 1.1 August 2009 CTP

This commit is contained in:
vineetc 2009-08-17 22:00:14 +00:00
parent c920eb693c
commit 273ea337e7
10 changed files with 2469 additions and 858 deletions

View file

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

View file

@ -1,11 +1,11 @@
* Notes about changes to the SQL Server Driver for PHP April 2009 Cumulative Update (CU) *
* Notes about changes to the SQL Server Driver for PHP 1.1 August 2009 CTP *
For details about the changes included in this CU, please see our blog at
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 April 2009 CU *
* Notes about compiling the SQL Server Driver for PHP 1.1 August 2009 CTP *
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,15 +32,14 @@ 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.6 and PHP 5.2.4
using the Visual C++ 2005 Express and Visual C++ 2008 Standard compilers.
This software has been compiled and tested under PHP 5.2.10 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
the license agreement may be found online at
http://www.codeplex.com/SQL2K5PHP/license.
http://www.codeplex.com/SQLSRVPHP/license.
* Note about version.h *
The version numbers in version.h in the source do not match the version numbers
in the supported PHP extension and have not been updated for this CU in the
published source code.
in the supported PHP extension.

929
conn.cpp
View file

@ -5,10 +5,8 @@
//
// Contents: Routines that use connection handles
//
// 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.
//----------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
@ -29,12 +27,17 @@ int current_log_subsystem = LOG_CONN;
// an arbitrary figure that should be large enough for most connection strings.
const int DEFAULT_CONN_STR_LEN = 2048;
// PHP streams generally return no more than 8k.
const int PHP_STREAM_BUFFER_SIZE = 8192;
// connection timeout string
// statement option for setting a query timeout
const char QUERY_TIMEOUT[] = "QueryTimeout";
// option for sending streams at execute time
// statement option for sending streams at execute time
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
// query options for cursor types
const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static";
const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic";
const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset";
const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward";
// statment option to create a scrollable result set
const char SCROLLABLE[] = "Scrollable";
// length of buffer used to retrieve information for client and server info buffers
const int INFO_BUFFER_LEN = 256;
// number of segments in a version resource
@ -52,11 +55,12 @@ struct _platform_url {
// *** 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,
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 );
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 );
void sqlsrv_conn_close_stmts( sqlsrv_conn* conn TSRMLS_DC );
}
@ -74,12 +78,18 @@ char* sqlsrv_conn::resource_name = "sqlsrv_conn";
namespace ConnOptions {
// most of these strings are the same for both the sqlsrv_connect connection option
// and the name put into the connection string. MARS is the only one that's different.
const char APP[] = "APP";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char DateAsString[] = "ReturnDatesAsStrings";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_Option[] = "MultipleActiveResultSets";
const char MARS_ODBC[] = "MARS_Connection";
const char PWD[] = "PWD";
const char QuotedId[] = "QuotedId";
const char TraceFile[] = "TraceFile";
@ -140,21 +150,17 @@ PHP_FUNCTION( sqlsrv_connect )
}
conn_str.reserve( DEFAULT_CONN_STR_LEN );
emalloc_auto_ptr<sqlsrv_conn> conn;
conn = static_cast<sqlsrv_conn*>( emalloc( sizeof( sqlsrv_conn )));
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
conn = new ( sqlsrv_malloc( sizeof( sqlsrv_conn ))) sqlsrv_conn;
hash_auto_ptr stmts;
ALLOC_HASHTABLE( stmts );
zr = zend_hash_init( stmts, 10, NULL, sqlsrv_stmt_hash_dtor, 0 );
zr = zend_hash_init( stmts, 10, NULL /*hash function*/, NULL /* dtor */, 0 /* persistent */ );
if( zr == FAILURE ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_ZEND_HASH TSRMLS_CC );
RETURN_FALSE;
}
conn->ctx.handle = NULL;
conn->ctx.handle_type = SQL_HANDLE_DBC;
conn->in_transaction = false;
SQLHANDLE henv = g_henv_cp; // by default use the connection pooling henv
// check the connection pooling setting to determine which henv to use to allocate the connection handle
if( options_z ) {
@ -188,9 +194,9 @@ PHP_FUNCTION( sqlsrv_connect )
CHECK_SQL_ERROR( r, SQLSRV_G( henv_context ), _FN_, NULL,
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = NULL;
RETURN_FALSE );
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = NULL;
RETURN_FALSE );
}
catch( std::bad_alloc& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
@ -214,20 +220,31 @@ PHP_FUNCTION( sqlsrv_connect )
RETURN_FALSE;
}
SQLSRV_STATIC_ASSERT( sizeof( char ) == sizeof( SQLCHAR )); // make sure that cast below is valid
r = SQLDriverConnect( conn->ctx.handle, NULL, reinterpret_cast<SQLCHAR*>( const_cast<char*>( conn_str.c_str() )),
static_cast<SQLSMALLINT>( conn_str.length() ), NULL,
// convert our connection string to UTF-16 before connecting with SQLDriverConnnectW
wchar_t* wconn_string;
unsigned int wconn_len = (conn_str.length() + 1) * sizeof( wchar_t );
wconn_string = utf16_string_from_mbcs_string( conn->default_encoding, conn_str.c_str(), conn_str.length(), &wconn_len );
if( wconn_string == NULL ) {
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = SQL_NULL_HANDLE;
RETURN_FALSE;
}
r = SQLDriverConnectW( conn->ctx.handle, NULL, reinterpret_cast<SQLWCHAR*>( wconn_string ),
static_cast<SQLSMALLINT>( wconn_len ), NULL,
0, &output_conn_size, SQL_DRIVER_NOPROMPT );
// Would rather use std::fill here, but that gives a warning about not being able to inline
// the iterator functions, so we use this instead.
// clear the connection string from memory to remove sensitive data (such as a password).
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn_str.clear();
sqlsrv_free( wconn_string );
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 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 );
@ -240,6 +257,15 @@ PHP_FUNCTION( sqlsrv_connect )
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle ); conn->ctx.handle = SQL_NULL_HANDLE; RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
r = determine_server_version( conn, _FN_ TSRMLS_CC );
if( !SQL_SUCCEEDED( r )) {
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = SQL_NULL_HANDLE;
RETURN_FALSE;
}
zr = ZEND_REGISTER_RESOURCE( return_value, conn, sqlsrv_conn::descriptor );
if( zr == FAILURE ) {
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
@ -309,8 +335,8 @@ PHP_FUNCTION( sqlsrv_client_info )
sqlsrv_conn* conn = NULL;
zval* client_info = NULL;
SQLSMALLINT info_len = 0;
emalloc_auto_ptr<char> buffer;
emalloc_auto_ptr<char> ver;
sqlsrv_malloc_auto_ptr<char> buffer;
sqlsrv_malloc_auto_ptr<char> ver;
DWORD ver_size = (~0U);
DWORD winRC = S_OK;
DWORD place_holder = 0;
@ -326,7 +352,7 @@ PHP_FUNCTION( sqlsrv_client_info )
zr = array_init( return_value );
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
rc = SQLGetInfo( conn->ctx.handle, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
@ -335,7 +361,7 @@ PHP_FUNCTION( sqlsrv_client_info )
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
buffer = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
rc = SQLGetInfo( conn->ctx.handle, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
@ -344,7 +370,7 @@ PHP_FUNCTION( sqlsrv_client_info )
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
buffer = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
rc = SQLGetInfo( conn->ctx.handle, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
@ -353,12 +379,12 @@ PHP_FUNCTION( sqlsrv_client_info )
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
buffer = static_cast<char*>( emalloc( MAX_PATH + 1 ));
buffer = static_cast<char*>( sqlsrv_malloc( MAX_PATH + 1 ));
winRC = GetModuleFileNameEx( GetCurrentProcess(), g_sqlsrv_hmodule, buffer, MAX_PATH );
CHECK_SQL_ERROR_EX( winRC == 0, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
ver_size = GetFileVersionInfoSize( buffer, &place_holder );
CHECK_SQL_ERROR_EX( ver_size == 0, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
ver = static_cast<char*>( emalloc( ver_size ) );
ver = static_cast<char*>( sqlsrv_malloc( ver_size ) );
winRC = GetFileVersionInfo( buffer, 0, ver_size, ver );
CHECK_SQL_ERROR_EX( winRC == FALSE, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
winRC = VerQueryValue( ver, "\\", reinterpret_cast<LPVOID*>( &ver_info ), &unused );
@ -430,8 +456,6 @@ PHP_FUNCTION( sqlsrv_close )
RETURN_FALSE;
}
sqlsrv_conn_common_close( conn, _FN_, true 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.
@ -454,9 +478,25 @@ void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC )
DECL_FUNC_NAME( "sqlsrv_conn_dtor" );
LOG_FUNCTION;
sqlsrv_conn_common_close( conn, _FN_, false TSRMLS_CC );
sqlsrv_conn_close_stmts( conn TSRMLS_CC );
efree( conn );
// rollback any transaction in progress (we don't care about the return result)
SQLEndTran( SQL_HANDLE_DBC, conn->ctx.handle, SQL_ROLLBACK );
// disconnect from the server
SQLRETURN r = SQLDisconnect( conn->ctx.handle );
if( !SQL_SUCCEEDED( r )) {
LOG( SEV_ERROR, LOG_CONN, "Disconnect failed when closing the connection." );
}
// free the connection handle
r = SQLFreeHandle( SQL_HANDLE_DBC, conn->ctx.handle );
if( !SQL_SUCCEEDED( r )) {
LOG( SEV_ERROR, LOG_CONN, "Failed to free the connection handle when destroying the connection resource" );
}
conn->ctx.handle = NULL;
sqlsrv_free( conn );
rsrc->ptr = NULL;
}
@ -550,7 +590,7 @@ PHP_FUNCTION( sqlsrv_prepare )
SQLSRV_UNUSED( return_value_ptr );
sqlsrv_conn* conn = NULL;
emalloc_auto_ptr<sqlsrv_stmt> stmt;
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
char *sql_string = NULL;
int sql_len = 0;
zval* params_z = NULL;
@ -569,33 +609,55 @@ PHP_FUNCTION( sqlsrv_prepare )
}
SQLSRV_STATIC_ASSERT( sizeof(SQLCHAR) == sizeof(char) );
r = SQLPrepare( stmt->ctx.handle, reinterpret_cast<SQLCHAR*>( sql_string ), SQL_NTS );
wchar_t* wsql_string;
unsigned int wsql_len = 0;
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
if( sql_len == 0 || ( sql_string[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( 1 ));
wsql_string[0] = '\0';
wsql_len = 0;
}
else {
wsql_string = utf16_string_from_mbcs_string( stmt->conn->default_encoding, reinterpret_cast<const char*>( sql_string ), sql_len,
&wsql_len );
if( wsql_string == NULL ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
free_odbc_resources( stmt TSRMLS_CC );
RETURN_FALSE;
}
}
r = SQLPrepareW( stmt->ctx.handle, reinterpret_cast<SQLWCHAR*>( wsql_string ), wsql_len );
sqlsrv_free( wsql_string );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, free_odbc_resources( stmt TSRMLS_CC ); RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
stmt->prepared = true;
if( !mark_params_by_reference( &params_z, _FN_ TSRMLS_CC )) {
free_odbc_resources( stmt TSRMLS_CC );
RETURN_FALSE;
}
if( !mark_params_by_reference( &params_z, _FN_ TSRMLS_CC )) {
free_odbc_resources( stmt TSRMLS_CC );
RETURN_FALSE;
}
stmt->params_z = params_z;
// register the statement with the PHP runtime
zval_auto_ptr stmt_z;
ALLOC_INIT_ZVAL( stmt_z );
int zr = ZEND_REGISTER_RESOURCE( stmt_z, stmt, sqlsrv_stmt::descriptor );
if( zr == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_REGISTER_RESOURCE TSRMLS_CC, "statement" );
RETURN_FALSE;
}
// store the resource id with the connection so the connection can release this statement
// when it closes.
next_index = zend_hash_next_free_element( conn->stmts );
if( zend_hash_index_update( conn->stmts, next_index, &stmt_z, sizeof( zval* ), NULL ) == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
long rsrc_idx = Z_RESVAL_P( stmt_z );
if( zend_hash_index_update( conn->stmts, next_index, &rsrc_idx, sizeof( long ), NULL /*output*/ ) == FAILURE ) {
stmt->conn = NULL; // tell the statement that it isn't part of the connection so don't try to remove itself
free_stmt_resource( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_ZEND_HASH TSRMLS_CC );
RETURN_FALSE;
}
@ -604,7 +666,6 @@ PHP_FUNCTION( sqlsrv_prepare )
zval_ptr_dtor( &return_value );
*return_value_ptr = stmt_z;
zval_add_ref( &stmt_z ); // two references to the zval, one returned and another in the connection
stmt_z.transferred();
}
@ -645,7 +706,7 @@ PHP_FUNCTION( sqlsrv_query )
SQLSRV_UNUSED( return_value_ptr );
sqlsrv_conn* conn = NULL;
emalloc_auto_ptr<sqlsrv_stmt> stmt;
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
SQLCHAR *sql_string = NULL;
int sql_len = 0;
zval* params_z = NULL;
@ -666,6 +727,7 @@ PHP_FUNCTION( sqlsrv_query )
// 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 );
stmt->free_param_data();
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
@ -680,23 +742,27 @@ PHP_FUNCTION( sqlsrv_query )
if( !executed ) {
free_odbc_resources( stmt TSRMLS_CC );
stmt->free_param_data();
RETURN_FALSE;
}
// register the statement with the PHP runtime
zval_auto_ptr stmt_z;
ALLOC_INIT_ZVAL( stmt_z );
int zr = ZEND_REGISTER_RESOURCE( stmt_z, stmt, sqlsrv_stmt::descriptor );
if( zr == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_REGISTER_RESOURCE TSRMLS_CC, "statement" );
RETURN_FALSE;
}
// store the resource id with the connection so the connection can release this statement
// when it closes.
next_index = zend_hash_next_free_element( conn->stmts );
if( zend_hash_index_update( conn->stmts, next_index, &stmt_z, sizeof( zval* ), NULL ) == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
long rsrc_idx = Z_RESVAL_P( stmt_z );
if( zend_hash_index_update( conn->stmts, next_index, &rsrc_idx, sizeof( long ), NULL /*output*/ ) == FAILURE ) {
stmt->conn = NULL; // tell the statement that it isn't part of the connection so don't try to remove itself
free_stmt_resource( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_ZEND_HASH TSRMLS_CC );
RETURN_FALSE;
}
@ -705,7 +771,6 @@ PHP_FUNCTION( sqlsrv_query )
zval_ptr_dtor( &return_value );
*return_value_ptr = stmt_z;
zval_add_ref( &stmt_z ); // two references to the zval, one returned and another in the connection
stmt_z.transferred();
}
@ -790,7 +855,7 @@ PHP_FUNCTION( sqlsrv_server_info )
int zr = SUCCESS;
sqlsrv_conn* conn = NULL;
zval* server_info;
emalloc_auto_ptr<char> p;
sqlsrv_malloc_auto_ptr<char> p;
SQLSMALLINT info_len;
DECL_FUNC_NAME( "sqlsrv_server_info" );
@ -801,7 +866,7 @@ PHP_FUNCTION( sqlsrv_server_info )
zr = array_init( return_value );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_SERVER_INFO, RETURN_FALSE );
p = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
p = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
r = SQLGetInfo( conn->ctx.handle, SQL_DATABASE_NAME, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
@ -812,7 +877,7 @@ PHP_FUNCTION( sqlsrv_server_info )
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
p.transferred();
p = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
p = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
r = SQLGetInfo( conn->ctx.handle, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
@ -822,7 +887,7 @@ PHP_FUNCTION( sqlsrv_server_info )
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_SERVER_INFO, RETURN_FALSE );
p.transferred();
p = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
p = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
r = SQLGetInfo( conn->ctx.handle, SQL_SERVER_NAME, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
@ -837,37 +902,55 @@ PHP_FUNCTION( sqlsrv_server_info )
namespace {
// common close, used by close and dtor
void sqlsrv_conn_common_close( __inout sqlsrv_conn* conn, const char* _FN_, bool check_errors TSRMLS_DC )
// must close all statement handles opened by this connection before closing the connection
// no errors are returned, since close should always succeed
void sqlsrv_conn_close_stmts( sqlsrv_conn* conn TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
// test prerequisites
if( conn->ctx.handle == NULL ) DIE( "Connection handle is NULL. Trying to destroy an already destroyed connection." );
if( !conn->stmts ) DIE( "Connection doesn't contain a statement array." );
if( conn->ctx.handle == NULL )
return;
// loop through the stmts hash table and destroy each stmt resource so we can close the
// ODBC connection
for( zend_hash_internal_pointer_reset( conn->stmts );
zend_hash_has_more_elements( conn->stmts ) == SUCCESS;
zend_hash_move_forward( conn->stmts )) {
// close the statements and roll back transactions
if( conn->stmts ) {
long* rsrc_idx_ptr;
zend_hash_destroy( conn->stmts );
FREE_HASHTABLE( conn->stmts );
conn->stmts = NULL;
}
else {
DIE( "Connection doesn't contain a statement array!" );
// get the resource id for the next statement created with this connection
int zr = zend_hash_get_current_data( conn->stmts, reinterpret_cast<void**>( &rsrc_idx_ptr ));
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_CONN, "Failed to retrieve a statement resource from the connection" );
}
long rsrc_idx = *rsrc_idx_ptr;
// see if the statement is still valid, and if not skip to the next one
// presumably this should never happen because if it's in the list, it should still be valid
// by virtue that a statement resource should remove itself from its connection when it is
// destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource
// and move to the next one.
sqlsrv_stmt* stmt;
int type;
stmt = static_cast<sqlsrv_stmt*>( zend_list_find( rsrc_idx, &type ));
if( stmt == NULL || type != sqlsrv_stmt::descriptor ) {
LOG( SEV_ERROR, LOG_CONN, "Non existent statement found in connection. Statements should remove themselves"
" from the connection so this shouldn't be out of sync." );
continue;
}
// delete the statement by deleting it from Zend's resource list, which will force its destruction
stmt->conn = NULL;
zr = zend_hash_index_del( &EG( regular_list ), rsrc_idx );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_CONN, "Failed to remove statement resource %1!d! when closing the connection", rsrc_idx );
}
}
// rollback any transaction in progress (we don't care about the return result)
SQLEndTran( SQL_HANDLE_DBC, conn->ctx.handle, SQL_ROLLBACK );
// disconnect from the server
r = SQLDisconnect( conn->ctx.handle );
if( check_errors ) { CHECK_SQL_ERROR( r, conn, _FN_, NULL, NULL, 1==1 ); }
// free the connection handle
r = SQLFreeHandle( SQL_HANDLE_DBC, conn->ctx.handle );
if( check_errors ) { CHECK_SQL_ERROR( r, conn, _FN_, NULL, 1 == 1 ); }
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
conn->ctx.handle = NULL;
zend_hash_destroy( conn->stmts );
FREE_HASHTABLE( conn->stmts );
conn->stmts = NULL;
}
#define NO_ATTRIBUTE -1
@ -879,131 +962,337 @@ enum CONN_ATTR_TYPE {
CONN_ATTR_STRING,
};
// list of valid attributes used by validate_connection_attribute below
struct connection_attribute {
const char * name;
int name_len;
// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or sets an attribute)
struct connection_option {
// the name of the option as passed in by the user
const char * sqlsrv_name;
unsigned int sqlsrv_len;
// the name of the option in the ODBC connection string
const char * odbc_name;
unsigned int odbc_len;
enum CONN_ATTR_TYPE value_type;
int attr;
bool add;
} conn_attrs[] = {
{ ConnOptions::APP, sizeof( ConnOptions::APP ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::ConnectionPooling, sizeof( ConnOptions::ConnectionPooling ), CONN_ATTR_BOOL, NO_ATTRIBUTE, false },
{ ConnOptions::Database, sizeof( ConnOptions::Database ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::Encrypt, sizeof( ConnOptions::Encrypt ), CONN_ATTR_BOOL, NO_ATTRIBUTE, true },
{ ConnOptions::Failover_Partner, sizeof( ConnOptions::Failover_Partner ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::LoginTimeout, sizeof( ConnOptions::LoginTimeout ), CONN_ATTR_INT, SQL_ATTR_LOGIN_TIMEOUT, true },
{ ConnOptions::PWD, sizeof( ConnOptions::PWD ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::QuotedId, sizeof( ConnOptions::QuotedId ), CONN_ATTR_BOOL, NO_ATTRIBUTE, true },
{ ConnOptions::TraceFile, sizeof( ConnOptions::TraceFile ), CONN_ATTR_STRING, SQL_ATTR_TRACEFILE, true },
{ ConnOptions::TraceOn, sizeof( ConnOptions::TraceOn ), CONN_ATTR_BOOL, SQL_ATTR_TRACE, true },
{ ConnOptions::TrustServerCertificate, sizeof( ConnOptions::TrustServerCertificate ), CONN_ATTR_BOOL, NO_ATTRIBUTE, true },
{ ConnOptions::TransactionIsolation, sizeof( ConnOptions::TransactionIsolation ), CONN_ATTR_INT, SQL_COPT_SS_TXN_ISOLATION, true },
{ ConnOptions::UID, sizeof( ConnOptions::UID ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::WSID, sizeof( ConnOptions::WSID ), CONN_ATTR_STRING, NO_ATTRIBUTE, true }
// process the connection type
// return whether or not the function was successful in processing the connection option
bool (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC );
};
// return structure from validate
typedef
struct _attr_return {
bool success; // if the attribute was validated
// if it's a connection attribute rather than a connection string keyword, this is set to the attribute (SQL_ATTR_*)
// or set to NO_ATTRIBUTE if it's not a connection attribute
int attr;
// the value of the attribute if it's a connection attribute rather than a connection string keyword
int value;
char* str_value; // connection string keyword if that's what it is
unsigned int str_len; // length of the connection string keyword (save ourselves a strlen)
bool add; // see build_connect_string_and_attr for this field's use
}
attr_return;
// connection attribute functions
template <unsigned int Attr>
struct int_conn_attr_func {
static bool func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
SQLRETURN r = SQLSetConnectAttr( conn->ctx.handle, Attr, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( value )), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, conn, "sqlsrv_connect", NULL, return false );
return true;
}
};
template <unsigned int Attr>
struct bool_conn_attr_func {
static bool func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
SQLRETURN r = SQLSetConnectAttr( conn->ctx.handle, Attr, reinterpret_cast<SQLPOINTER>( zend_is_true( value )), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, conn, "sqlsrv_connect", NULL, return false );
return true;
}
};
template <unsigned int Attr>
struct str_conn_attr_func {
static bool func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
SQLRETURN r = SQLSetConnectAttr( conn->ctx.handle, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )), Z_STRLEN_P( value ));
CHECK_SQL_ERROR( r, conn, "sqlsrv_connect", NULL, return false );
return true;
}
};
// boolean connection string
struct bool_conn_str_func {
static bool func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC )
{
TSRMLS_C;
char const* val_str;
if( zend_is_true( value )) {
val_str = "yes";
}
else {
val_str = "no";
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
return true;
}
};
// simply add the parsed value to the connection string
struct conn_str_append_func {
static bool func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC )
{
// wrap a connection option in a quote. It is presumed that any charactes that need to be escaped will
// be escaped, such as a closing }.
TSRMLS_C;
const char* val_str = Z_STRVAL_P( value );
int val_len = Z_STRLEN_P( value );
if( val_len > 0 && val_str[0] == '{' && val_str[ val_len - 1 ] == '}' ) {
++val_str;
val_len -= 2;
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str.append( val_str, val_len );
conn_str += "};";
return true;
}
};
// validates a single key/value pair from the attributes given to sqlsrv_connect.
struct conn_char_set_func {
static bool func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
convert_to_string( value );
const char* encoding = Z_STRVAL_P( value );
unsigned int encoding_len = Z_STRLEN_P( value );
for( zend_hash_internal_pointer_reset( SQLSRV_G( encodings ));
zend_hash_has_more_elements( SQLSRV_G( encodings )) == SUCCESS;
zend_hash_move_forward( SQLSRV_G( encodings ) ) ) {
sqlsrv_encoding* table_encoding;
zend_hash_get_current_data( SQLSRV_G( encodings ), (void**) &table_encoding );
if( encoding_len == table_encoding->iana_len &&
!stricmp( encoding, table_encoding->iana )) {
if( table_encoding->not_for_connection ) {
handle_error( &conn->ctx, LOG_CONN, "sqlsrv_connect", SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING TSRMLS_CC, encoding );
return false;
}
conn->default_encoding = table_encoding->code_page;
return true;
}
}
handle_error( &conn->ctx, LOG_CONN, "sqlsrv_connect", SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING TSRMLS_CC, encoding );
return false;
}
};
struct date_as_string_func {
static bool func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
TSRMLS_C; // show as used to avoid a warning
if( zend_is_true( value )) {
conn->date_as_string = true;
}
else {
conn->date_as_string = false;
}
return true;
}
};
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
struct conn_null_func {
static bool func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC )
{
TSRMLS_C;
return true;
}
};
// list of valid attributes used by validate_connection_option below
const connection_option conn_opts[] = {
{
ConnOptions::APP,
sizeof( ConnOptions::APP ),
ConnOptions::APP,
sizeof( ConnOptions::APP ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
ConnOptions::CharacterSet,
sizeof( ConnOptions::CharacterSet ),
ConnOptions::CharacterSet,
sizeof( ConnOptions::CharacterSet ),
CONN_ATTR_STRING,
conn_char_set_func::func
},
{
ConnOptions::ConnectionPooling,
sizeof( ConnOptions::ConnectionPooling ),
ConnOptions::ConnectionPooling,
sizeof( ConnOptions::ConnectionPooling ),
CONN_ATTR_BOOL,
conn_null_func::func
},
{
ConnOptions::Database,
sizeof( ConnOptions::Database ),
ConnOptions::Database,
sizeof( ConnOptions::Database ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
ConnOptions::DateAsString,
sizeof( ConnOptions::DateAsString ),
ConnOptions::DateAsString,
sizeof( ConnOptions::DateAsString ),
CONN_ATTR_BOOL,
date_as_string_func::func
},
{
ConnOptions::Encrypt,
sizeof( ConnOptions::Encrypt ),
ConnOptions::Encrypt,
sizeof( ConnOptions::Encrypt ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
ConnOptions::Failover_Partner,
sizeof( ConnOptions::Failover_Partner ),
ConnOptions::Failover_Partner,
sizeof( ConnOptions::Failover_Partner ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
ConnOptions::LoginTimeout,
sizeof( ConnOptions::LoginTimeout ),
ConnOptions::LoginTimeout,
sizeof( ConnOptions::LoginTimeout ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
},
{
ConnOptions::MARS_Option,
sizeof( ConnOptions::MARS_Option ),
ConnOptions::MARS_ODBC,
sizeof( ConnOptions::MARS_ODBC ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
ConnOptions::PWD,
sizeof( ConnOptions::PWD ),
ConnOptions::PWD,
sizeof( ConnOptions::PWD ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
ConnOptions::QuotedId,
sizeof( ConnOptions::QuotedId ),
ConnOptions::QuotedId,
sizeof( ConnOptions::QuotedId ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
ConnOptions::TraceFile,
sizeof( ConnOptions::TraceFile ),
ConnOptions::TraceFile,
sizeof( ConnOptions::TraceFile ),
CONN_ATTR_STRING,
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
},
{
ConnOptions::TraceOn,
sizeof( ConnOptions::TraceOn ),
ConnOptions::TraceOn,
sizeof( ConnOptions::TraceOn ),
CONN_ATTR_BOOL,
bool_conn_attr_func<SQL_ATTR_TRACE>::func
},
{
ConnOptions::TransactionIsolation,
sizeof( ConnOptions::TransactionIsolation ),
ConnOptions::TransactionIsolation,
sizeof( ConnOptions::TransactionIsolation ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_COPT_SS_TXN_ISOLATION>::func
},
{
ConnOptions::TrustServerCertificate,
sizeof( ConnOptions::TrustServerCertificate ),
ConnOptions::TrustServerCertificate,
sizeof( ConnOptions::TrustServerCertificate ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
ConnOptions::UID,
sizeof( ConnOptions::UID ),
ConnOptions::UID,
sizeof( ConnOptions::UID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
ConnOptions::WSID,
sizeof( ConnOptions::WSID ),
ConnOptions::WSID,
sizeof( ConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
};
// validates a single key/value pair from the options given to sqlsrv_connect.
// to validate means to verify that it is a legal key from the list of keys in conn_attrs (above)
// and to verify the type.
// string attributes are scanned to make sure that all } are properly escaped as }}.
const attr_return validate_connection_attribute( sqlsrv_conn const* conn, const char* key, int key_len, zval const* value_z TSRMLS_DC )
connection_option const* validate_connection_option( sqlsrv_conn const* conn, const char* key, unsigned int key_len, zval const* value_z TSRMLS_DC )
{
int attr_idx = 0;
attr_return ret;
int opt_idx = 0;
// initialize our default return values
ret.success = false;
ret.attr = NO_ATTRIBUTE;
ret.value = 0;
ret.str_value = NULL;
ret.str_len = 0;
for( attr_idx = 0; attr_idx < ( sizeof( conn_attrs ) / sizeof( conn_attrs[0] )); ++attr_idx ) {
for( opt_idx = 0; opt_idx < ( sizeof( conn_opts ) / sizeof( conn_opts[0] )); ++opt_idx ) {
if( key_len == conn_attrs[ attr_idx ].name_len && !stricmp( key, conn_attrs[ attr_idx ].name )) {
if( key_len == conn_opts[ opt_idx ].sqlsrv_len && !stricmp( key, conn_opts[ opt_idx ].sqlsrv_name )) {
switch( conn_attrs[ attr_idx ].value_type ) {
switch( conn_opts[ opt_idx ].value_type ) {
case CONN_ATTR_BOOL:
// bool attributes can be either strings to be appended to the connection string
// as yes or no or integral connection attributes. This will have to be reworked
// if we ever introduce a boolean connection option that maps to a string connection
// attribute.
ret.success = true;
ret.attr = conn_attrs[ attr_idx ].attr;
ret.add = conn_attrs[ attr_idx ].add;
// here we short circuit the ConnectionPooling option because it was already handled
if( conn_attrs[ attr_idx ].name == ConnOptions::ConnectionPooling ) {
return ret;
}
if( zend_is_true( const_cast<zval*>( value_z ))) {
if( ret.attr == NO_ATTRIBUTE ) {
ret.str_value = estrdup( "yes" ); // for connection strings
ret.str_len = 3;
}
else {
ret.value = true; // for connection attributes
}
}
else {
if( ret.attr == NO_ATTRIBUTE ) {
ret.str_value = estrdup( "no" ); // for connection strings
ret.str_len = 2;
}
else {
ret.value = false; // for connection attributes
}
}
break;
case CONN_ATTR_INT:
{
CHECK_SQL_ERROR_EX( Z_TYPE_P( value_z ) != IS_LONG, conn, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION, ret.success = false; return ret; );
ret.value = Z_LVAL_P( value_z );
ret.attr = conn_attrs[ attr_idx ].attr;
ret.success = true;
ret.add = conn_attrs[ attr_idx ].add;
return ret;
CHECK_SQL_ERROR_EX( Z_TYPE_P( value_z ) != IS_LONG, conn, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION, return NULL; );
break;
}
case CONN_ATTR_STRING:
{
CHECK_SQL_ERROR_EX( Z_TYPE_P( value_z ) != IS_STRING, conn, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION, ret.success = false; return ret; );
CHECK_SQL_ERROR_EX( Z_TYPE_P( value_z ) != IS_STRING, conn, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION, return NULL );
char* value = Z_STRVAL_P( value_z );
int value_len = Z_STRLEN_P( value_z );
ret.success = true;
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// unquoted since we quote it when adding it to the connection string.
if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
++value;
--value_len;
value = estrndup( value, value_len );
value[ value_len - 1 ] = '\0';
value_len -= 2;
}
// duplicate the string so when we free it, it won't be a user string we're freeing
else {
value = estrndup( value, value_len );
}
ret.attr = conn_attrs[ attr_idx ].attr;
ret.add = conn_attrs[ attr_idx ].add;
ret.str_value = value;
ret.str_len = value_len;
// check to make sure that all right braces are escaped
int i = 0;
while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) {
@ -1012,23 +1301,20 @@ const attr_return validate_connection_attribute( sqlsrv_conn const* conn, const
++i;
++i;
}
if( value[i] == '}' ) {
if( i < value_len && value[i] == '}' ) {
handle_error( &conn->ctx, LOG_CONN, "sqlsrv_connect", SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED TSRMLS_CC, key );
efree( value );
ret.str_value = NULL;
ret.success = false;
return ret;
return NULL;
}
break;
}
}
return ret;
return &conn_opts[ opt_idx ];
}
}
handle_error( &conn->ctx, LOG_CONN, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION TSRMLS_CC, key );
return ret;
return NULL;
}
@ -1037,17 +1323,18 @@ const attr_return validate_connection_attribute( sqlsrv_conn const* conn, const
// passed to the connection, and then break them out ourselves and either set attributes or put the
// option in the connection string.
SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, const char* server, zval const* options,
SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, zval const* options,
__inout std::string& connection_string TSRMLS_DC )
{
bool credentials_mentioned = false;
attr_return ret;
bool mars_mentioned = false;
connection_option const* conn_opt;
int zr = SUCCESS;
DECL_FUNC_NAME( "sqlsrv_connect" );
// put the driver and server as the first components of the connection string
connection_string = "Driver={SQL Native Client};Server=";
connection_string = "Driver={SQL Server Native Client 10.0};Server=";
connection_string += server;
connection_string += ";";
@ -1077,42 +1364,22 @@ SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, co
zr = zend_hash_get_current_data( oht, (void**) &data );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return SQL_ERROR; );
ret = validate_connection_attribute( conn, key, key_len, *data TSRMLS_CC );
if( !ret.success ) {
conn_opt = validate_connection_option( conn, key, key_len, *data TSRMLS_CC );
if( !conn_opt ) {
return SQL_ERROR;
}
// if a user id is given ,then don't use a trusted connection
if( !stricmp( key, "UID" )) {
if( ( key_len == sizeof( ConnOptions::UID )) && !stricmp( key, ConnOptions::UID )) {
credentials_mentioned = true;
}
if( (key_len == sizeof( ConnOptions::MARS_Option )) && !stricmp( key, ConnOptions::MARS_Option )) {
mars_mentioned = true;
}
if( NO_ATTRIBUTE == ret.attr ) {
// some options are already handled (e.g., ConnectionPooling) so don't add them to the connection string
if( ret.add ) {
// if it's not an attribute, then it's a connection string keyword. Add it quoted.
connection_string += key;
connection_string += "={";
connection_string += ret.str_value;
connection_string += "};";
}
}
else {
SQLRETURN r;
// if it's a string attribute, the str_value member will be the string to set, otherwise the str_value will be null
// and ret.value will be the integer value to set the attribute to.
if( ret.str_value == NULL ) {
r = SQLSetConnectAttr( conn->ctx.handle, ret.attr, reinterpret_cast<SQLPOINTER>( ret.value ), SQL_IS_UINTEGER );
}
else {
r = SQLSetConnectAttr( conn->ctx.handle, ret.attr, reinterpret_cast<SQLPOINTER>( const_cast<char*>( ret.str_value )), ret.str_len );
}
CHECK_SQL_ERROR( r, conn, "sqlsrv_connect", NULL, return SQL_ERROR );
CHECK_SQL_WARNING( r, conn, "sqlsrv_connect", NULL );
}
if( ret.str_value != NULL ) {
efree( ret.str_value );
bool f = conn_opt->func( conn_opt, *data, conn, connection_string TSRMLS_CC );
if( !f ) {
return SQL_ERROR;
}
}
@ -1120,12 +1387,134 @@ SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, co
if( !credentials_mentioned ) {
connection_string += "Trusted_Connection={Yes};";
}
// always have mars enabled.
connection_string += "Mars_Connection={Yes};";
// MARS on if not explicitly turned off
if( !mars_mentioned ) {
connection_string += "MARS_Connection={Yes};";
}
return SQL_SUCCESS;
}
struct stmt_option;
struct stmt_option_functor {
virtual bool operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/,
const char* /*_FN_*/ TSRMLS_DC )
{
TSRMLS_C;
DIE( "Not implemented" );
return false;
}
};
// used to hold the table for statment options
struct stmt_option {
const char* name;
long name_len;
// callback that actually handles the work of the option
stmt_option_functor* func;
};
struct stmt_option_query_timeout : public stmt_option_functor {
virtual bool operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z, const char* _FN_ TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
if( Z_TYPE_P( value_z ) != IS_LONG ) {
convert_to_string( value_z );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_VALUE TSRMLS_CC, Z_STRVAL_P( value_z ), opt->name );
return false;
}
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( value_z )), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
char lock_timeout_sql[ 32 ];
int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", Z_LVAL_P( value_z ) * 1000 );
if( written == -1 || written == sizeof( lock_timeout_sql )) {
DIE( "sprintf_s failed. Shouldn't ever fail." );
}
r = SQLExecDirect( stmt->ctx.handle, reinterpret_cast<SQLCHAR*>( lock_timeout_sql ), SQL_NTS );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
return true;
}
};
struct stmt_option_send_at_exec : public stmt_option_functor {
virtual bool operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z, const char* /*_FN_*/ TSRMLS_DC )
{
TSRMLS_C;
stmt->send_at_exec = ( zend_is_true( value_z )) ? true : false;
return true;
}
};
struct stmt_option_scrollable : public stmt_option_functor {
virtual bool operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z, const char* _FN_ TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
if( Z_TYPE_P( value_z ) != IS_STRING ) {
handle_error( &stmt->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE TSRMLS_CC );
return false;
}
const char* scroll_type = Z_STRVAL_P( value_z );
// keep the flag for use by other procedures rather than have to query ODBC for the value
stmt->scrollable = true;
stmt->scroll_is_dynamic = false;
// find which cursor type they would like and set the ODBC statement attribute as such
if( !stricmp( scroll_type, QUERY_OPTION_SCROLLABLE_STATIC )) {
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_CURSOR_TYPE, reinterpret_cast<SQLPOINTER>( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
}
else if( !stricmp( scroll_type, QUERY_OPTION_SCROLLABLE_DYNAMIC )) {
stmt->scroll_is_dynamic = true; // this cursor is dynamic
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_CURSOR_TYPE, reinterpret_cast<SQLPOINTER>( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
}
else if( !stricmp( scroll_type, QUERY_OPTION_SCROLLABLE_KEYSET )) {
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_CURSOR_TYPE, reinterpret_cast<SQLPOINTER>( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
}
// use a boring, old (but very performant :)) forward only cursor
else if( !stricmp( scroll_type, QUERY_OPTION_SCROLLABLE_FORWARD )) {
stmt->scrollable = false; // reset since forward isn't scrollable
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_CURSOR_TYPE, reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
}
// didn't match any of the cursor types
else {
handle_error( &stmt->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE TSRMLS_CC );
return false;
}
return true;
}
};
struct stmt_option stmt_opts[] = {
{ QUERY_TIMEOUT, sizeof( QUERY_TIMEOUT ), new stmt_option_query_timeout },
{ SEND_STREAMS_AT_EXEC, sizeof( SEND_STREAMS_AT_EXEC ), new stmt_option_send_at_exec },
{ SCROLLABLE, sizeof( SCROLLABLE ), new stmt_option_scrollable }
};
// return the option from the stmt_opts array that matches the key. If no option found,
// NULL is returned.
stmt_option* validate_stmt_option( const char* key, long key_len )
{
for( int i = 0; i < sizeof( stmt_opts ) / sizeof( stmt_option ); ++i ) {
// if we find the key we're looking for, return it
if( key_len == stmt_opts[ i ].name_len && !stricmp( stmt_opts[ i ].name, key )) {
return &stmt_opts[ i ];
}
}
return NULL; // no option found
}
// common code to allocate a statement from either sqlsrv_prepare or sqlsv_query. Returns either
// a valid sqlsrv_stmt or NULL if an error occurred.
@ -1133,28 +1522,41 @@ SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, co
sqlsrv_stmt* allocate_stmt( __in sqlsrv_conn* conn, zval const* options_z, char const* _FN_ TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
emalloc_auto_ptr<sqlsrv_stmt> stmt;
stmt = static_cast<sqlsrv_stmt*>( emalloc( sizeof( sqlsrv_stmt )));
emalloc_auto_ptr<char> param_buffer;
param_buffer = static_cast<char*>( emalloc( PHP_STREAM_BUFFER_SIZE ));
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
stmt = new ( sqlsrv_malloc( sizeof( sqlsrv_stmt ))) sqlsrv_stmt;
sqlsrv_malloc_auto_ptr<char> param_buffer;
param_buffer = static_cast<char*>( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE ));
// we don't put all this initialization in a constructor since it would serve little purpose
// and would complicate the handling of the allocation/deallocation of param_buffer member.
// The param_buffer member would have to be freed within a destructor if it were allocated
// within the constructor, which would preclude the use of the sqlsrv_auto_ptr. Also, the
// instance itself would have to use the placement new or override new since we must use
// emalloc rather than the default new. And since this is the only place we allocate the
// statement, it's better to just keep it localized to here.
stmt->ctx.handle_type = SQL_HANDLE_STMT;
stmt->conn = conn;
stmt->executed = false;
stmt->prepared = false;
stmt->current_parameter = NULL;
stmt->current_parameter_read = 0;
stmt->current_stream = NULL;
stmt->current_stream_read = 0;
stmt->current_stream_encoding = SQLSRV_ENCODING_CHAR;
stmt->params_z = NULL;
stmt->params_ind_ptr = NULL;
stmt->param_datetime_buffers = NULL;
stmt->param_output_strings = NULL;
stmt->param_strings = NULL;
stmt->param_streams = NULL;
stmt->param_buffer = param_buffer;
stmt->param_buffer_size = PHP_STREAM_BUFFER_SIZE;
stmt->send_at_exec = true;
stmt->conn_index = -1;
stmt->fetch_fields = NULL;
stmt->fetch_fields_count = 0;
stmt->scrollable = false;
stmt->scroll_is_dynamic = false;
stmt->has_rows = false;
stmt->new_result_set();
stmt->active_stream = NULL;
@ -1188,29 +1590,17 @@ sqlsrv_stmt* allocate_stmt( __in sqlsrv_conn* conn, zval const* options_z, char
int zr = zend_hash_get_current_data( Z_ARRVAL_P( options_z ), (void**) &value_z );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return NULL );
if( !stricmp( key, QUERY_TIMEOUT )) {
if( Z_TYPE_P( *value_z ) != IS_LONG ) {
convert_to_string( *value_z );
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_VALUE TSRMLS_CC, Z_STRVAL_PP( value_z ), key );
stmt_option* stmt_opt = validate_stmt_option( key, key_len );
// if the key didn't match, then return the error to the script
if( !stmt_opt ) {
handle_error( &stmt->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_KEY TSRMLS_CC, key );
SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle );
return NULL;
}
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( *value_z )), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle ); return NULL; );
char lock_timeout_sql[ 1024 ];
int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", Z_LVAL_PP( value_z ) * 1000 );
if( written == -1 || written == sizeof( lock_timeout_sql )) {
DIE( "sprintf_s failed. Shouldn't ever fail." );
}
r = SQLExecDirect( stmt->ctx.handle, reinterpret_cast<SQLCHAR*>( lock_timeout_sql ), SQL_NTS );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle ); return NULL; );
}
else if( key_len == ( sizeof( SEND_STREAMS_AT_EXEC )) && !stricmp( key, SEND_STREAMS_AT_EXEC )) {
stmt->send_at_exec = ( zend_is_true( *value_z )) ? true : false;
}
// if didn't match one of the standard options, then the key is an error
else {
handle_error( &stmt->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_KEY TSRMLS_CC, key );
// perform the actions the statement option needs done
bool attr_set = (*stmt_opt->func)( stmt, stmt_opt, *value_z, _FN_ TSRMLS_CC );
// any errors should have been posted in the callback
if( !attr_set ) {
SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle );
return NULL;
}
@ -1228,7 +1618,8 @@ sqlsrv_stmt* allocate_stmt( __in sqlsrv_conn* conn, zval const* options_z, char
// mark parameters passed into sqlsrv_prepare and sqlsrv_query as reference parameters so that
// they may be updated later in the script and subsequent sqlsrv_execute calls will use the
// new values.
// new values. Marking them as references "pins" them to their memory location so that
// the buffer we give to ODBC can be relied on to be there.
bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS_DC )
{
@ -1258,6 +1649,7 @@ 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<void**>( &param ), NULL );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, zval_ptr_dtor( params_zz ); return false; );
@ -1266,12 +1658,12 @@ bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS
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<char> warning;
warning = static_cast<char*>( emalloc( warning_len ));
sqlsrv_malloc_auto_ptr<char> warning;
warning = static_cast<char*>( sqlsrv_malloc( 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
Z_SET_ISREF_PP( param ); // mark it as a reference
}
// else mark [0] as a reference
else {
@ -1285,12 +1677,12 @@ bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS
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<char> warning;
warning = static_cast<char*>( emalloc( warning_len ));
sqlsrv_malloc_auto_ptr<char> warning;
warning = static_cast<char*>( sqlsrv_malloc( warning_len ));
snprintf( warning, warning_len, PHP_WARNING_VAR_NOT_REFERENCE->native_message, i );
php_error( E_WARNING, warning );
}
Z_SET_ISREF_PP( var );
Z_SET_ISREF_PP( var ); // mark it as a reference
}
}
@ -1317,4 +1709,41 @@ struct _platform_url* get_driver_info( void )
}
}
// some features require a server of a certain version or later
// this function determines the version of the server we're connected to
// and stores it in the connection. Any errors are logged before return.
// SQL_ERROR is returned when the server version is either undetermined
// or is invalid (< 2000).
SQLRETURN determine_server_version( sqlsrv_conn* conn, const char* _FN_ TSRMLS_DC )
{
SQLSMALLINT info_len;
char p[ INFO_BUFFER_LEN ];
SQLRETURN r = SQLGetInfo( conn->ctx.handle, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, return r; );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
char version_major_str[ 3 ];
SERVER_VERSION version_major;
memcpy( version_major_str, p, 2 );
version_major_str[ 2 ] = '\0';
version_major = static_cast<SERVER_VERSION>( atoi( version_major_str ));
if( 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 );
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;
}
}
} // namespace

View file

@ -3,12 +3,12 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: The initialization routines for the SQL Server Driver for PHP 1.0
// Contents: initialization routines for the extension
//
// 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.
//----------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
@ -105,6 +105,10 @@ ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, "type" )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
@ -113,6 +117,10 @@ ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 )
ZEND_ARG_INFO( 0, "connection resource" )
ZEND_ARG_INFO( 0, "sql command" )
@ -135,11 +143,11 @@ ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 )
ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 )
ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 )
ZEND_ARG_INFO( 0, "statement resource" )
ZEND_END_ARG_INFO()
@ -174,8 +182,10 @@ zend_function_entry sqlsrv_functions[] = {
PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo )
PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo )
PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo )
PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo )
PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo )
PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo )
PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo )
PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo )
PHP_FE( sqlsrv_query, sqlsrv_query_arginfo )
PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo )
@ -192,7 +202,7 @@ zend_function_entry sqlsrv_functions[] = {
PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo )
{NULL, NULL, NULL}
{NULL, NULL, NULL} // end of the table
};
// module global variables (initialized in MINIT and freed in MSHUTDOWN)
@ -343,6 +353,22 @@ PHP_MINIT_FUNCTION(sqlsrv)
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_DATE;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIME2;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET;
constant_type.typeinfo.size = 34;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 27;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS );
@ -354,6 +380,18 @@ PHP_MINIT_FUNCTION(sqlsrv)
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", "forward", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", "static", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", "dynamic", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", "keyset", CONST_PERSISTENT | CONST_CS );
if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT_FUNCTION: stream registration failed" );
return FAILURE;
@ -371,9 +409,7 @@ PHP_MINIT_FUNCTION(sqlsrv)
return FAILURE;
}
sqlsrv_henv henv_ctx;
henv_ctx.ctx.handle = g_henv_ncp;
henv_ctx.ctx.handle_type = SQL_HANDLE_ENV;
sqlsrv_henv henv_ctx( g_henv_cp );
// set to ODBC 3
r = SQLSetEnvAttr( g_henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER );
@ -448,8 +484,6 @@ PHP_MSHUTDOWN_FUNCTION(sqlsrv)
{
SQLSRV_UNUSED( type );
LOG( SEV_NOTICE, LOG_INIT, "sqlsrv: entering mshutdown" );
UNREGISTER_INI_ENTRIES();
if( g_henv_ncp != SQL_NULL_HANDLE ) {
@ -491,13 +525,11 @@ PHP_RINIT_FUNCTION(sqlsrv)
SQLSRV_G( log_subsystems ) = 0;
SQLSRV_G( log_severity ) = SEV_ERROR;
SQLSRV_G( warnings_return_as_errors ) = true;
SQLSRV_G( henv_context ) = static_cast<sqlsrv_henv*>( emalloc( sizeof( sqlsrv_henv )));
SQLSRV_G( henv_context )->ctx.handle = g_henv_cp;
SQLSRV_G( henv_context )->ctx.handle_type = SQL_HANDLE_ENV;
SQLSRV_G( henv_context ) = new ( sqlsrv_malloc( sizeof( sqlsrv_henv ))) sqlsrv_henv( g_henv_cp );
ALLOC_INIT_ZVAL( SQLSRV_G( errors ));
Z_SET_ISREF_PP( &SQLSRV_G( errors ) );
Z_SET_ISREF_P( SQLSRV_G( errors ));
ALLOC_INIT_ZVAL( SQLSRV_G( warnings ));
Z_SET_ISREF_PP( &SQLSRV_G( warnings ) );
Z_SET_ISREF_P( SQLSRV_G( warnings ));
// read INI settings
SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS );
@ -561,19 +593,38 @@ PHP_RINIT_FUNCTION(sqlsrv)
to_ignore.native_message = NULL;
to_ignore.native_code = -1;
to_ignore.format = false;
zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL );
zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
return FAILURE;
}
// packet size warning
to_ignore.sqlstate = "01S02";
to_ignore.native_message = NULL;
to_ignore.native_code = 0;
to_ignore.format = false;
zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL );
// supported encodings
ALLOC_HASHTABLE( SQLSRV_G( encodings ));
zr = zend_hash_init( SQLSRV_G( encodings ), 5, NULL /*use standard hash function*/, NULL /*no resource destructor*/, 0 /*not persistent*/ );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" );
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR );
zr = zend_hash_next_index_insert( SQLSRV_G( encodings ), &sql_enc_char, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true );
zr = zend_hash_next_index_insert( SQLSRV_G( encodings ), &sql_enc_bin, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 );
zr = zend_hash_next_index_insert( SQLSRV_G( encodings ), &sql_enc_utf8, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
@ -601,7 +652,7 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv)
zend_hash_destroy( SQLSRV_G( warnings_to_ignore ));
FREE_HASHTABLE( SQLSRV_G( warnings_to_ignore ));
}
efree( SQLSRV_G( henv_context ));
sqlsrv_free( SQLSRV_G( henv_context ));
// verify memory at the end of the request (in debug mode only)
full_mem_check(MEMCHECK_SILENT);

View file

@ -6,12 +6,12 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: Declarations for the SQL Server Driver for PHP 1.0
// Contents: Declarations for the extension
//
// Comments: Also contains "internal" declarations shared across source files.
//
// 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 HAVE_CONFIG_H
@ -32,6 +32,7 @@ OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." )
OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." )
OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." )
OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." )
OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." )
#endif
extern "C" {
@ -96,6 +97,8 @@ OACR_WARNING_POP
#define SQL_SS_UDT (-151)
#define SQL_COPT_SS_TXN_ISOLATION 1227
#define SQL_TXN_SS_SNAPSHOT 0x00000020L
#define SQL_SS_TIME2 (-154)
#define SQL_SS_TIMESTAMPOFFSET (-155)
// static assert for enforcing compile time conditions
template <bool b>
@ -107,162 +110,15 @@ struct sqlsrv_static_assert<true> { 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)
#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
// Constants and Types for sqlsrv data types and encodings
//**********************************************************************************************************************************
// module initialization
PHP_MINIT_FUNCTION(sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(sqlsrv);
// sqlsrv_context
// a sqlsrv_context is the agnostic way to represent a handle and its type. This is used primarily when handling errors and
// warnings. We pass this in and the error handling can use the handle and its type to get the diagnostic records from
// SQLGetDiagRec.
struct sqlsrv_context {
SQLHANDLE handle;
SQLSMALLINT handle_type;
};
// variables set during initialization (move these to init.cpp)
extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP
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
//**********************************************************************************************************************************
// Connection
//**********************************************************************************************************************************
// *** connection resource structure ***
// this is the resource structure returned when a connection is made.
struct sqlsrv_conn {
// instance variables
sqlsrv_context ctx; // see sqlsrv_context
HashTable* stmts; // collection of statements allocated from this connection
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
// static variables used in process_params
static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here.
static int descriptor;
};
// environment context used by sqlsrv_connect for when a connection error occurs.
struct sqlsrv_henv {
sqlsrv_context ctx;
};
// *** connection functions ***
PHP_FUNCTION(sqlsrv_connect);
PHP_FUNCTION(sqlsrv_begin_transaction);
PHP_FUNCTION(sqlsrv_client_info);
PHP_FUNCTION(sqlsrv_close);
PHP_FUNCTION(sqlsrv_commit);
PHP_FUNCTION(sqlsrv_query);
PHP_FUNCTION(sqlsrv_prepare);
PHP_FUNCTION(sqlsrv_rollback);
PHP_FUNCTION(sqlsrv_server_info);
// resource destructor
void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
//**********************************************************************************************************************************
// Statement
//**********************************************************************************************************************************
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field {
char* name;
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 free_param_data( void );
void new_result_set( void );
sqlsrv_context ctx;
sqlsrv_conn* conn;
zval* current_parameter;
unsigned int current_parameter_read;
bool executed;
bool prepared;
bool fetch_called;
// field names for the current result set for use by sqlsrv_fetch_array/object as keys
sqlsrv_fetch_field* fetch_fields;
int fetch_fields_count;
int last_field_index;
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;
int conn_index;
zval* active_stream;
// static variables used in process_params
static char* resource_name; // char because const char forces casting all over the place in ODBC functions
static int descriptor;
};
// *** statement functions ***
PHP_FUNCTION(sqlsrv_cancel);
PHP_FUNCTION(sqlsrv_execute);
PHP_FUNCTION(sqlsrv_fetch);
PHP_FUNCTION(sqlsrv_fetch_array);
PHP_FUNCTION(sqlsrv_fetch_object);
PHP_FUNCTION(sqlsrv_field_metadata);
PHP_FUNCTION(sqlsrv_free_stmt);
PHP_FUNCTION(sqlsrv_get_field);
PHP_FUNCTION(sqlsrv_next_result);
PHP_FUNCTION(sqlsrv_num_fields);
PHP_FUNCTION(sqlsrv_rows_affected);
PHP_FUNCTION(sqlsrv_send_stream_data);
// resource destructor
void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
// "internal" statement functions used by sqlsrv_query and sqlsrv_close
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function TSRMLS_DC );
void sqlsrv_stmt_hash_dtor( void* stmt );
void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC );
void free_php_resources( zval* stmt_z TSRMLS_DC );
void remove_from_connection( sqlsrv_stmt* stmt TSRMLS_DC );
// *** constants ***
// types for conversions on output parameters (though they can be used for input parameters, they are ignored)
enum SQLSRV_PHPTYPE {
MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type
@ -282,6 +138,8 @@ enum SQLSRV_ENCODING {
SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables.
SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData
SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData
SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR,
SQLSRV_ENCODING_DEFAULT, // use what is the connection's default
};
// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object.
@ -318,12 +176,242 @@ union sqlsrv_phptype {
struct typeinfo_t {
unsigned type:8;
int encoding:16;
unsigned encoding:16;
} typeinfo;
long value;
};
// supported server versions (determined at connection time)
enum SERVER_VERSION {
SERVER_VERSION_UNKNOWN = -1,
SERVER_VERSION_2000 = 8,
SERVER_VERSION_2005,
SERVER_VERSION_2008, // use this for anything 2008 or later
};
//**********************************************************************************************************************************
// Initialization Functions
//**********************************************************************************************************************************
// module initialization
PHP_MINIT_FUNCTION(sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(sqlsrv);
// sqlsrv_context
// a sqlsrv_context is the agnostic way to represent a handle and its type. This is used primarily when handling errors and
// warnings. We pass this in and the error handling can use the handle and its type to get the diagnostic records from
// SQLGetDiagRec.
struct sqlsrv_context {
SQLHANDLE handle;
SQLSMALLINT handle_type;
sqlsrv_context( SQLSMALLINT type ) :
handle( SQL_NULL_HANDLE ),
handle_type( type )
{
}
sqlsrv_context( SQLHANDLE h, SQLSMALLINT t ) :
handle( h ),
handle_type( t )
{
}
};
// variables set during initialization (move these to init.cpp)
extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP
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
// maps an IANA encoding to a code page
struct sqlsrv_encoding {
const char* iana;
unsigned int iana_len;
unsigned int code_page;
bool not_for_connection;
sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ):
iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn )
{
}
};
//**********************************************************************************************************************************
// Connection
//**********************************************************************************************************************************
// *** connection resource structure ***
// this is the resource structure returned when a connection is made.
struct sqlsrv_conn {
// instance variables
sqlsrv_context ctx; // see sqlsrv_context
HashTable* stmts; // collection of statements allocated from this connection
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
bool date_as_string; // date/datetime/datetimeoffset/etc. fields return as strings rather than PHP DateTime objects
unsigned int default_encoding; // encoding set with the "CharSet" connection option
SERVER_VERSION server_version; // version of the server that we're connected to
// initialize with default values
sqlsrv_conn( void ) :
ctx( SQL_HANDLE_DBC ),
stmts( NULL ),
in_transaction( false ),
default_encoding( SQLSRV_ENCODING_CHAR ),
date_as_string( false )
{
}
// static variables used in process_params
static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here.
static int descriptor;
};
// environment context used by sqlsrv_connect for when a connection error occurs.
struct sqlsrv_henv {
sqlsrv_context ctx;
sqlsrv_henv( SQLHANDLE handle ) :
ctx( handle, SQL_HANDLE_ENV )
{
}
};
// *** connection functions ***
PHP_FUNCTION(sqlsrv_connect);
PHP_FUNCTION(sqlsrv_begin_transaction);
PHP_FUNCTION(sqlsrv_client_info);
PHP_FUNCTION(sqlsrv_close);
PHP_FUNCTION(sqlsrv_commit);
PHP_FUNCTION(sqlsrv_query);
PHP_FUNCTION(sqlsrv_prepare);
PHP_FUNCTION(sqlsrv_rollback);
PHP_FUNCTION(sqlsrv_server_info);
// resource destructor
void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
//**********************************************************************************************************************************
// Statement
//**********************************************************************************************************************************
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field {
char* name;
unsigned int len;
};
// holds the stream param and the encoding that it was assigned
struct sqlsrv_stream_encoding {
zval* stream_z;
unsigned int encoding;
sqlsrv_stream_encoding( zval* str_z, unsigned int enc ) :
stream_z( str_z ), encoding( enc )
{
}
};
// holds the string output parameter information
struct sqlsrv_output_string {
zval* string_z;
unsigned int encoding;
int param_num; // used to index into the ind_or_len of the statement
sqlsrv_output_string( zval* str_z, unsigned int enc, int num ) :
string_z( str_z ), encoding( enc ), param_num( num )
{
}
};
// *** statement resource structure ***
struct sqlsrv_stmt {
void free_param_data( void );
void new_result_set( void );
sqlsrv_context ctx; // context that holds the statement handle
sqlsrv_conn* conn; // connection that created this statement
zval* current_stream; // current stream sending data to the server as an input parameter
unsigned int current_stream_read; // if we read an empty PHP stream, we send an empty string to the server
unsigned int current_stream_encoding; // code page of the stream's encoding
bool executed; // whether the statement has been executed yet (used for error messages)
bool prepared; // whether the statement has been prepared yet (used for error messages)
bool fetch_called; // used by sqlsrv_get_field to return an informative error if fetch not yet called
// (a common mistake)
sqlsrv_fetch_field* fetch_fields; // field names for the current result set for use by
// sqlsrv_fetch_array/object as keys
int fetch_fields_count;
int last_field_index; // last field retrieved by sqlsrv_get_field
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
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
void* param_buffer; // bufffer which param data from streams is read in and processed
int param_buffer_size;
bool send_at_exec; // determines if all the data is sent from a stream input parameter when sqlsrv_execute is called
int conn_index; // index into the connection hash that contains this statement structure
zval* active_stream; // the currently active stream reading data from the database
bool scrollable; // determines if the statement was created with the Scrollable query attribute
// (don't have to use ODBC to find out)
bool scroll_is_dynamic; // if scrollable, is it a dynamic cursor. sqlsrv_num_rows uses this information
bool has_rows; // has_rows is set if there are actual rows in the row set
sqlsrv_stmt( void ) :
ctx( SQL_HANDLE_STMT )
{
}
// static variables used in process_params
static char* resource_name; // char because const char forces casting all over the place in ODBC functions
static int descriptor;
};
// *** statement functions ***
PHP_FUNCTION(sqlsrv_cancel);
PHP_FUNCTION(sqlsrv_execute);
PHP_FUNCTION(sqlsrv_fetch);
PHP_FUNCTION(sqlsrv_fetch_array);
PHP_FUNCTION(sqlsrv_fetch_object);
PHP_FUNCTION(sqlsrv_field_metadata);
PHP_FUNCTION(sqlsrv_free_stmt);
PHP_FUNCTION(sqlsrv_get_field);
PHP_FUNCTION(sqlsrv_has_rows);
PHP_FUNCTION(sqlsrv_next_result);
PHP_FUNCTION(sqlsrv_num_fields);
PHP_FUNCTION(sqlsrv_num_rows);
PHP_FUNCTION(sqlsrv_rows_affected);
PHP_FUNCTION(sqlsrv_send_stream_data);
// resource destructor
void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
// "internal" statement functions shared by functions in conn.cpp and stmt.cpp
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function TSRMLS_DC );
void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC );
void free_stmt_resource( zval* stmt_z TSRMLS_DC );
// *** constants ***
// *** variables ***
@ -369,6 +457,10 @@ struct sqlsrv_stream {
extern php_stream_wrapper g_sqlsrv_stream_wrapper;
// buffer size allocated to retrieve data from a PHP stream. This number
// was chosen since PHP doesn't return more than 8k at a time even if
// the amount requested was more.
const int PHP_STREAM_BUFFER_SIZE = 8192;
//**********************************************************************************************************************************
// Global variables
@ -390,6 +482,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;
HashTable* encodings;
ZEND_END_MODULE_GLOBALS(sqlsrv)
@ -455,6 +548,48 @@ enum logging_severity {
// a macro to log entering a function. used at the top of each API function.
#define LOG_FUNCTION LOG( SEV_NOTICE, LOG_STMT, "%1!s!: entering", _FN_ );
// new memory allocation/free debugging facilities to help us verify that all allocations are being
// released in a timely manner and not just at the end of the script.
// Zend has memory logging and checking, but it can generate a lot of noise for just one extension.
// It's meant for internal use but might be useful for people adding features to our extension.
// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases
// must be done with sqlsrv_malloc and sqlsrv_free.
// #define SQLSRV_MEM_DEBUG 1
#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG )
// macro to log memory allocation and frees locations and their sizes
inline void* emalloc_trace( size_t size, const char* file, int line )
{
void* ptr = emalloc( size );
LOG( SEV_NOTICE, LOG_STMT, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr );
return ptr;
}
inline void* erealloc_trace( void* original, size_t size, const char* file, int line )
{
void* ptr = erealloc( original, size );
LOG( SEV_NOTICE, LOG_STMT, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original );
return ptr;
}
inline void efree_trace( void* ptr, const char* file, int line )
{
LOG( SEV_NOTICE, LOG_STMT, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line );
efree( ptr );
}
#define sqlsrv_malloc( size ) emalloc_trace( size, __FILE__, __LINE__ )
#define sqlsrv_realloc( buffer, size ) erealloc_trace( buffer, size, __FILE__, __LINE__ )
#define sqlsrv_free( ptr ) efree_trace( ptr, __FILE__, __LINE__ )
#else
#define sqlsrv_malloc( size ) emalloc( size )
#define sqlsrv_realloc( buffer, size ) erealloc( buffer, size )
#define sqlsrv_free( ptr ) efree( ptr )
#endif
//**********************************************************************************************************************************
// Configuration
@ -523,15 +658,30 @@ 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_INPUT_PARAM_ENCODING_TRANSLATE[];
extern sqlsrv_error SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE[];
extern sqlsrv_error SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE[];
extern sqlsrv_error SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE[];
extern sqlsrv_error SQLSRV_ERROR_INVALID_CONN_ENCODING[];
extern sqlsrv_error SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE[];
extern sqlsrv_error SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE[];
extern sqlsrv_error SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING[];
extern sqlsrv_error SQLSRV_ERROR_DRIVER_NOT_INSTALLED[];
extern sqlsrv_error SQLSRV_ERROR_MARS_OFF[];
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[];
// definitions for PHP specific warnings returned by sqlsrv
extern sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[];
// definitios for PHP warnings returned via php_error
// definitions for PHP warnings returned via php_error rather than sqlsrv_errors
extern sqlsrv_error PHP_WARNING_VAR_NOT_REFERENCE[];
// flags passed to sqlsrv_errors to filter its return values
enum error_handling_flags {
SQLSRV_ERR_ERRORS,
SQLSRV_ERR_WARNINGS,
@ -542,12 +692,27 @@ enum error_handling_flags {
PHP_FUNCTION(sqlsrv_errors);
PHP_FUNCTION(sqlsrv_warnings);
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string.
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string,
unsigned int mbcs_len, __out wchar_t* utf16_out_string,
unsigned int utf16_len );
// create a wide char string from the passed in mbcs string. NULL is returned if the string
// could not be created. No error is posted by this function. utf16_len is the number of
// wchar_t characters, not the number of bytes.
wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string,
unsigned int mbcs_len, __out unsigned int* utf16_len );
// *** internal error macros and functions ***
bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp TSRMLS_DC, ... );
void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp TSRMLS_DC, ... );
void __cdecl sqlsrv_error_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
const char* get_last_error_message( DWORD last_error = 0 );
// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which
// we don't want on a web server
@ -958,18 +1123,20 @@ public:
return _ptr;
}
// cast to T** used by many functions
T** operator&()
{
return &_ptr;
}
// value from reference operator (i.e., i = *(&i); or *i = blah;)
T& operator*()
{
return *_ptr;
}
// allow the use of the address-of operator to simulate a **.
// Note: this operator conflicts with storing these within an STL container. If you need
// to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp()
T** operator&( void )
{
return &_ptr;
}
protected:
sqlsrv_auto_ptr( T* ptr ) :
@ -977,7 +1144,7 @@ protected:
{
}
sqlsrv_auto_ptr( sqlsrv_auto_ptr const& src )
sqlsrv_auto_ptr( sqlsrv_auto_ptr& src )
{
if( _ptr ) {
static_cast<Subclass*>(this)->reset( src._ptr );
@ -985,14 +1152,6 @@ protected:
src.transferred();
}
sqlsrv_auto_ptr( typename Subclass const& src )
{
if( _ptr ) {
static_cast<Subclass*>( this )->reset( src._ptr );
}
src.transferred();
}
// assign a new pointer to the auto_ptr. It will free the previous memory block
// because ownership is deemed finished.
T* operator=( T* ptr )
@ -1006,36 +1165,43 @@ protected:
};
// an auto_ptr for emalloc/efree. When allocating a chunk of memory using emalloc, wrap that pointer
// in a variable of emalloc_auto_ptr. emalloc_auto_ptr will "own" that block and assure that it is
// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer
// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is
// freed until the variable is destroyed (out of scope) or ownership is transferred using the function
// "transferred".
template <typename T>
class emalloc_auto_ptr : public sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> > {
class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> > {
public:
emalloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> >( NULL )
sqlsrv_malloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( NULL )
{
}
emalloc_auto_ptr( const emalloc_auto_ptr& src )
sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( src )
{
sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> >::sqlsrv_auto_ptr( src );
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( T* ptr = NULL )
{
if( _ptr )
efree( (void*) _ptr );
sqlsrv_free( (void*) _ptr );
_ptr = ptr;
}
T* operator=( T* ptr )
{
return sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> >::operator=( ptr );
return sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >::operator=( ptr );
}
sqlsrv_malloc_auto_ptr<T> operator=( sqlsrv_malloc_auto_ptr<T>& src )
{
T* p = src.get();
src.transferred();
this->_ptr = p;
}
};
@ -1100,13 +1266,10 @@ public:
{
return sqlsrv_auto_ptr<zval, zval_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*()
operator zval_gc_info*( void )
{
return reinterpret_cast<zval_gc_info*>( _ptr );
return reinterpret_cast<zval_gc_info*>(_ptr);
}
#endif

1523
stmt.cpp
View file

@ -6,7 +6,7 @@
// Contents: Routines that use statement handles
//
// 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.
//----------------------------------------------------------------------------------------------------------------------------------
// *** header files ***
@ -37,22 +37,35 @@ const int SQLSRV_SIZE_MAX_TYPE = -1;
// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx
const char DATETIME_CLASS_NAME[] = "DateTime";
const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1;
const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P";
const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT );
const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u";
const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT );
const char DATE_FORMAT[] = "Y-m-d";
const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT );
// constants for maximums in SQL Server
// 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;
// constant strings used for the field metadata results
// (char to avoid having to cast them where they are used)
char* FIELD_METADATA_NAME = "Name";
char* FIELD_METADATA_TYPE = "Type";
char* FIELD_METADATA_SIZE = "Size";
char* FIELD_METADATA_PREC = "Precision";
char* FIELD_METADATA_SCALE = "Scale";
char* FIELD_METADATA_NULLABLE = "Nullable";
// base allocation size when retrieving a string field
const int INITIAL_FIELD_STRING_LEN = 256;
// max size of a date time string when converting from a DateTime object to a string
const int MAX_DATETIME_STRING_LEN = 256;
// this must align 1:1 with the SQLSRV_PHPTYPE enum in php_sqlsrv.h
const zend_uchar sqlsrv_to_php_type[] = {
const zend_uchar sqlsrv_to_zend_phptype[] = {
IS_NULL,
IS_LONG,
IS_DOUBLE,
@ -62,34 +75,68 @@ const zend_uchar sqlsrv_to_php_type[] = {
SQLSRV_PHPTYPE_INVALID
};
// map a Zend PHP type constant to our constant type
enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
SQLSRV_PHPTYPE_NULL,
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_STREAM,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID
};
// default class used when no class is specified by sqlsrv_fetch_object
const char STDCLASS_NAME[] = "stdclass";
const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1;
// UTF-8 tags for byte length of characters
const unsigned int UTF8_MIDBYTE_MASK = 0xc0;
const unsigned int UTF8_MIDBYTE_TAG = 0x80;
const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0;
const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0;
const unsigned int UTF8_3BYTESEQ_TAG = 0xe0;
const unsigned int UTF8_4BYTESEQ_TAG = 0xf0;
const unsigned int UTF8_NBYTESEQ_MASK = 0xf0;
// the message returned by SQL Native Client
const char CONNECTION_BUSY_ODBC_ERROR[] = "[Microsoft][SQL Server Native Client 10.0]Connection is busy with results for another command";
// *** 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 );
bool adjust_output_lengths_and_encodings( 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 );
bool calc_string_size( sqlsrv_stmt const* s, SQLUSMALLINT field_index, SQLUINTEGER& size, const char* _FN_ TSRMLS_DC );
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 );
SQLSMALLINT determine_c_type( int php_type, int encoding );
bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, SQLUINTEGER* column_size, SQLSMALLINT* decimal_digits );
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,
sqlsrv_sqltype& sql_type, SQLSMALLINT& sql_c_type, SQLUINTEGER& column_size, SQLSMALLINT& decimal_digits TSRMLS_DC );
sqlsrv_phptype determine_sqlsrv_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string );
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 );
sqlsrv_sqltype& sql_type, SQLSMALLINT& sql_c_type, SQLUINTEGER& column_size, SQLSMALLINT& decimal_digits,
sqlsrv_phptype& sqlsrv_phptype TSRMLS_DC );
sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string );
sqlsrv_sqltype determine_sql_type( zval const* value, int encoding, SERVER_VERSION server_version );
bool determine_stmt_has_rows( sqlsrv_stmt* stmt, const char* _FN_ TSRMLS_DC );
void fetch_common( sqlsrv_stmt* stmt, int fetch_type, long fetch_style, long fetch_offset, 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 );
void get_field_as_string( sqlsrv_stmt const* s, sqlsrv_phptype sqlsrv_phptype, SQLUSMALLINT field_index, zval* return_value, const char* _FN_ TSRMLS_DC );
SQLRETURN has_result_columns( sqlsrv_stmt* stmt, bool& result_present );
SQLRETURN has_any_result( sqlsrv_stmt* stmt, bool& result_present );
bool is_fixed_size_type( SQLINTEGER sql_type );
bool is_streamable_type( SQLINTEGER sql_type );
bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type );
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type );
zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval* param_array, SQLSMALLINT param_num, int& direction, zend_uchar& php_type, SQLSMALLINT& sql_c_type, sqlsrv_sqltype& sql_type,
SQLUINTEGER& column_size, SQLSMALLINT& decimal_digits TSRMLS_DC );
SQLUINTEGER& column_size, SQLSMALLINT& decimal_digits, sqlsrv_phptype& sqlsrv_phptype TSRMLS_DC );
bool send_stream_packet( sqlsrv_stmt* stmt, zval* return_value, char const* _FN_ TSRMLS_DC );
bool should_be_converted_from_utf16( SQLINTEGER sql_type );
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type );
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type );
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type );
@ -195,16 +242,20 @@ PHP_FUNCTION( sqlsrv_free_stmt )
}
}
// verify the resource so we know we're deleting a statement
stmt = static_cast<sqlsrv_stmt*>( zend_fetch_resource( &stmt_r TSRMLS_CC, -1, "sqlsrv_stmt", NULL, 1, sqlsrv_stmt::descriptor ));
if( stmt == NULL ) {
handle_error( NULL, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
free_odbc_resources( stmt TSRMLS_CC );
// delete the resource from Zend's master list, which will trigger the statement's destructor
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 %1!d!", Z_RESVAL_P( stmt_r ));
}
// this frees up the php resources as well
remove_from_connection( stmt TSRMLS_CC );
ZVAL_NULL( stmt_r );
RETURN_TRUE;
}
@ -240,7 +291,7 @@ PHP_FUNCTION( sqlsrv_execute )
CHECK_SQL_ERROR_EX( stmt->prepared == false, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_PREPARED, RETURN_FALSE );
// reset the parameter currently sending
stmt->current_parameter = NULL;
stmt->current_stream = NULL;
// execute the prepared statement (false means use SQLExecute rather than SQLExecDirect
executed = sqlsrv_stmt_common_execute( stmt, NULL, 0, false, _FN_ TSRMLS_CC );
@ -271,30 +322,50 @@ PHP_FUNCTION( sqlsrv_fetch )
{
SQLRETURN r = SQL_SUCCESS;
sqlsrv_stmt* stmt = NULL;
SQLSMALLINT num_cols;
long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
long fetch_offset = 0; // default value for parameter if one isn't supplied
DECL_FUNC_NAME( "sqlsrv_fetch" );
LOG_FUNCTION;
// take only the statement resource
PROCESS_PARAMS( stmt, _FN_, "r" );
PROCESS_PARAMS( stmt, _FN_, "r|ll", &fetch_style, &fetch_offset );
// verify the fetch style
CHECK_SQL_ERROR_EX( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE,
stmt, _FN_, SQLSRV_ERROR_INVALID_FETCH_STYLE, RETURN_FALSE );
// make sure the statement has been executed
CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE );
CHECK_SQL_ERROR_EX( stmt->past_fetch_end, stmt, _FN_, SQLSRV_ERROR_FETCH_PAST_END, RETURN_FALSE );
r = SQLNumResultCols( stmt->ctx.handle, &num_cols );
bool has_result_set;
r = has_result_columns( stmt, has_result_set );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
CHECK_SQL_ERROR_EX( num_cols == 0, stmt, _FN_, SQLSRV_ERROR_NO_FIELDS, RETURN_FALSE );
CHECK_SQL_ERROR_EX( !has_result_set, stmt, _FN_, SQLSRV_ERROR_NO_FIELDS, RETURN_FALSE );
close_active_stream( stmt TSRMLS_CC );
// move to the next record
r = SQLFetch( stmt->ctx.handle );
// if the statement has rows and is not scrollable but doesn't yet have
// fetch_called, this must be the first time we've called sqlsrv_fetch.
// Since we called we called SQLFetch in determine_stmt_has_rows, we're alreeady at the first row,
// so just return true and mark fetch_called as true so it will skip this clause next time
if( !stmt->scrollable && stmt->has_rows && !stmt->fetch_called ) {
stmt->fetch_called = true;
RETURN_TRUE;
}
// move to the record requested. For absolute records, we use a 0 based offset, so +1 since
// SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the number they give us
r = SQLFetchScroll( stmt->ctx.handle, static_cast<SQLSMALLINT>( fetch_style ),
( fetch_style == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 );
// return Zend NULL if we're at the end of the result set.
if( r == SQL_NO_DATA ) {
stmt->past_fetch_end = true;
// if this is a forward only cursor, mark that we've passed the end so future calls result in an error
if( !stmt->scrollable ) {
stmt->past_fetch_end = true;
}
RETURN_NULL();
}
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
@ -303,6 +374,7 @@ PHP_FUNCTION( sqlsrv_fetch )
// mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved
stmt->fetch_called = true;
stmt->last_field_index = -1;
stmt->has_rows = true; // since we made it his far, we must have at least one row
RETURN_TRUE;
}
@ -329,16 +401,19 @@ PHP_FUNCTION( sqlsrv_fetch_array )
{
sqlsrv_stmt* stmt = NULL;
int fetch_type = SQLSRV_FETCH_BOTH;
long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
long fetch_offset = 0; // default value for parameter if one isn't supplied
DECL_FUNC_NAME( "sqlsrv_fetch_array" );
LOG_FUNCTION;
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE)
PROCESS_PARAMS( stmt, _FN_, "r|l", &fetch_type );
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
PROCESS_PARAMS( stmt, _FN_, "r|lll", &fetch_type, &fetch_style, &fetch_offset );
// retrieve the hash table directly into the return_value variable for return. Any errors
// are handled directly in fetch_common.
fetch_common( stmt, fetch_type, return_value, _FN_, true TSRMLS_CC );
fetch_common( stmt, fetch_type, fetch_style, fetch_offset, return_value, _FN_, true TSRMLS_CC );
}
@ -385,20 +460,43 @@ PHP_FUNCTION( sqlsrv_fetch_object )
sqlsrv_stmt* stmt = NULL;
int zr = SUCCESS;
// stdClass is the name of the system's default base class in PHP
emalloc_auto_ptr<char> stdclass_name;
sqlsrv_malloc_auto_ptr<char> stdclass_name;
stdclass_name = estrdup( STDCLASS_NAME );
zval* class_name_z = NULL;
char* class_name = stdclass_name;
int class_name_len = STDCLASS_NAME_LEN;
zval* ctor_params_z = NULL;
long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
long fetch_offset = 0; // default value for parameter if one isn't supplied
DECL_FUNC_NAME( "sqlsrv_fetch_object" );
LOG_FUNCTION;
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE)
PROCESS_PARAMS( stmt, _FN_, "r|sa", &class_name, &class_name_len, &ctor_params_z );
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
// we also use z! instead of s and a so that null may be passed in as valid values for
// the class name and ctor params
PROCESS_PARAMS( stmt, _FN_, "r|z!z!ll", &class_name_z, &ctor_params_z,
&fetch_style, &fetch_offset );
// if the class name isn't null, then it must be a string, and set the class name to the string passed
if( class_name_z ) {
if( Z_TYPE_P( class_name_z ) != IS_STRING ) {
handle_error( NULL, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
else {
class_name = Z_STRVAL_P( class_name_z );
class_name_len = Z_STRLEN_P( class_name_z );
}
}
// if the constructor parameters array is not null, then it must be an array
if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) {
handle_error( NULL, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
// fetch the fields into an associative hash table
fetch_common( stmt, SQLSRV_FETCH_ASSOC, return_value, _FN_, false TSRMLS_CC );
fetch_common( stmt, SQLSRV_FETCH_ASSOC, fetch_style, fetch_offset, return_value, _FN_, false TSRMLS_CC );
if( Z_TYPE_P( return_value ) != IS_ARRAY ) {
return;
}
@ -449,14 +547,14 @@ PHP_FUNCTION( sqlsrv_fetch_object )
if( (*class_entry)->constructor ) {
// take the parameters given as our last argument and put them into a sequential array
emalloc_auto_ptr<zval**> params_m;
sqlsrv_malloc_auto_ptr<zval**> params_m;
zval_auto_ptr ctor_retval_z;
ALLOC_INIT_ZVAL( ctor_retval_z );
int num_params = 0;
if( ctor_params_z != NULL ) {
HashTable* ctorp_ht = Z_ARRVAL_P( ctor_params_z );
num_params = zend_hash_num_elements( ctorp_ht );
params_m = reinterpret_cast<zval***>( emalloc( num_params * sizeof( zval**) ));
params_m = reinterpret_cast<zval***>( sqlsrv_malloc( num_params * sizeof( zval**) ));
int i;
for( i = 0, zend_hash_internal_pointer_reset( ctorp_ht );
@ -539,7 +637,7 @@ PHP_FUNCTION( sqlsrv_field_metadata )
SQLSMALLINT num_cols = -1;
SQLUSMALLINT field_name_len_max = USHRT_MAX;
SQLSMALLINT temp = 0;
emalloc_auto_ptr<char> field_name_temp;
sqlsrv_malloc_auto_ptr<char> field_name_temp;
SQLSMALLINT field_name_len = -1;
SQLSMALLINT field_type = 0;
SQLULEN field_size = ULONG_MAX;
@ -569,12 +667,12 @@ PHP_FUNCTION( sqlsrv_field_metadata )
for( SQLSMALLINT f = 1; f <= num_cols; ++f ) {
field_name_temp = static_cast<char*>( emalloc( field_name_len_max + 1 ));
field_name_temp = static_cast<char*>( sqlsrv_malloc( field_name_len_max + 1 ));
// retrieve the field information
SQLSRV_STATIC_ASSERT( sizeof( char ) == sizeof( SQLCHAR ));
r = SQLDescribeCol( stmt->ctx.handle, f, reinterpret_cast<SQLCHAR*>( field_name_temp.get()), field_name_len_max, &field_name_len,
&field_type, &field_size, &field_scale, &field_is_nullable );
&field_type, &field_size, &field_scale, &field_is_nullable );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
@ -584,23 +682,27 @@ PHP_FUNCTION( sqlsrv_field_metadata )
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
// add the name to the array
zr = add_assoc_string( field_meta_data, "Name", field_name_temp, 0 );
zr = add_assoc_string( field_meta_data, FIELD_METADATA_NAME, field_name_temp, 0 );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
field_name_temp.transferred();
// add the type to the array
zr = add_assoc_long( field_meta_data, "Type", field_type );
zr = add_assoc_long( field_meta_data, FIELD_METADATA_TYPE, field_type );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
// depending on field type, we add the values into size or precision/scale and NULL out the other fields
switch( field_type ) {
case SQL_DECIMAL:
case SQL_NUMERIC:
zr = add_assoc_null( field_meta_data, "Size" );
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
zr = add_assoc_null( field_meta_data, FIELD_METADATA_SIZE );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
zr = add_assoc_long( field_meta_data, "Precision", field_size );
zr = add_assoc_long( field_meta_data, FIELD_METADATA_PREC, field_size );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
zr = add_assoc_long( field_meta_data, "Scale", field_scale );
zr = add_assoc_long( field_meta_data, FIELD_METADATA_SCALE, field_scale );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
break;
case SQL_BIT:
@ -611,25 +713,25 @@ PHP_FUNCTION( sqlsrv_field_metadata )
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
zr = add_assoc_null( field_meta_data, "Size" );
zr = add_assoc_null( field_meta_data, FIELD_METADATA_SIZE );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
zr = add_assoc_long( field_meta_data, "Precision", field_size );
zr = add_assoc_long( field_meta_data, FIELD_METADATA_PREC, field_size );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
zr = add_assoc_null( field_meta_data, "Scale" );
zr = add_assoc_null( field_meta_data, FIELD_METADATA_SCALE );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
break;
default:
zr = add_assoc_long( field_meta_data, "Size", field_size );
zr = add_assoc_long( field_meta_data, FIELD_METADATA_SIZE, field_size );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
zr = add_assoc_null( field_meta_data, "Precision" );
zr = add_assoc_null( field_meta_data, FIELD_METADATA_PREC );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
zr = add_assoc_null( field_meta_data, "Scale" );
zr = add_assoc_null( field_meta_data, FIELD_METADATA_SCALE );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
break;
}
// add the nullability to the array
zr = add_assoc_long( field_meta_data, "Nullable", field_is_nullable );
zr = add_assoc_long( field_meta_data, FIELD_METADATA_NULLABLE, field_is_nullable );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE );
// add this field's meta data to the result set meta data
@ -699,6 +801,7 @@ PHP_FUNCTION( sqlsrv_get_field )
// if they didn't specify a type as a parameter (so it still equals its initial value of PHPTYPE_INVALID)
if( sqlsrv_php_type.typeinfo.type == PHPTYPE_INVALID ) {
// get the SQL type of the field
r = SQLColAttribute( stmt->ctx.handle, static_cast<SQLUSMALLINT>( field_index + 1 ), SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &field_type );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
@ -709,7 +812,7 @@ PHP_FUNCTION( sqlsrv_get_field )
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
// get the default type to return
sqlsrv_php_type = determine_sqlsrv_php_type( field_type, field_len, false );
sqlsrv_php_type = determine_sqlsrv_php_type( stmt, field_type, field_len, false );
}
// verify that we have an acceptable type to convert.
@ -720,11 +823,56 @@ PHP_FUNCTION( sqlsrv_get_field )
}
// sqlsrv_has_rows( resource $stmt )
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// Returns whether or not there are rows waiting to be processed. There are two scenarios
// for using a function like this:
// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this.
// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least
// one row of data.
// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as
// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed
// until sqlsrv_fetch returns NULL.
// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error
// most likely means that there is no result data anyways.
// If this functions returs true one time, then it will return true even after the result set is exhausted (sqlsrv_fetch returns null)
PHP_FUNCTION( sqlsrv_has_rows )
{
sqlsrv_stmt* stmt = NULL;
DECL_FUNC_NAME( "sqlsrv_has_rows" );
LOG_FUNCTION;
// get the statement resource
PROCESS_PARAMS( stmt, _FN_, "r" );
if( !stmt->fetch_called ) {
determine_stmt_has_rows( stmt, _FN_ TSRMLS_CC );
}
if( stmt->has_rows ) {
RETURN_TRUE;
}
else {
RETURN_FALSE;
}
}
// sqlsrv_next_result( resource $stmt )
//
// Makes the next result (result set, row count, or output parameter) of the
// specified statement active. The first (or only) result returned by a batch
// query or stored procedure is active without a call to sqlsrv_next_result.
// Any output parameters bound are only available after sqlsrv_next_result returns
// null as per SQL Native Client specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx
//
// Parameters
// $stmt: The executed statement on which the next result is made active.
@ -751,20 +899,27 @@ PHP_FUNCTION( sqlsrv_next_result )
close_active_stream( stmt TSRMLS_CC );
stmt->has_rows = false;
// call the ODBC API that does what we want
r = SQLMoreResults( stmt->ctx.handle );
if( r == SQL_NO_DATA ) {
if( stmt->param_strings ) {
if( stmt->param_output_strings ) {
if( !adjust_output_string_lengths( stmt, _FN_ TSRMLS_CC )) {
// if we're finished processing result sets, handle the output string parameters
bool converted = adjust_output_lengths_and_encodings( stmt, _FN_ TSRMLS_CC );
if( !converted ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
RETURN_FALSE;
}
}
}
// if we're at the end, then return NULL
stmt->past_next_result_end = true;
RETURN_NULL();
}
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
@ -809,6 +964,45 @@ PHP_FUNCTION( sqlsrv_num_fields )
}
// sqlsrv_num_rows( resource $stmt )
//
// Retrieves the number of rows in an active result set. The statement must
// have been created with the Scrollable attribute set to 'static'.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of rows in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_rows )
{
SQLRETURN r = SQL_SUCCESS;
sqlsrv_stmt* stmt = NULL;
SQLINTEGER rows = -1;
DECL_FUNC_NAME( "sqlsrv_num_rows" );
LOG_FUNCTION;
// get the statement resource
PROCESS_PARAMS( stmt, _FN_, "r" );
// make sure it was created as scrollable and already executed
// if the cursor is dynamic, then the number of rows returned is always -1, so we issue an error if the cursor is dynamic
CHECK_SQL_ERROR_EX( !stmt->scrollable || stmt->scroll_is_dynamic, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, RETURN_FALSE );
CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE );
// retrieve the number of columns from ODBC
r = SQLRowCount( stmt->ctx.handle, &rows );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
// return it to the script
RETURN_LONG( rows );
}
// sqlsrv_rows_affected( resource $stmt )
//
// Returns the number of rows modified by the last statement executed. This
@ -838,6 +1032,9 @@ PHP_FUNCTION( sqlsrv_rows_affected )
// make sure it was executed
CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE );
// make sure it is not scrollable. This function should only work for inserts, updates, and deletes,
// but this is the best we can do to enforce that.
CHECK_SQL_ERROR_EX( stmt->scrollable, stmt, _FN_, SQLSRV_ERROR_STATEMENT_SCROLLABLE, RETURN_FALSE );
// get the row count from ODBC...
r = SQLRowCount( stmt->ctx.handle, &rows );
@ -957,13 +1154,51 @@ PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR)
// *** helper functions ***
// These are functions that deal with statement objects but are called from other modules, such as connection (conn.cpp)
// These are functions that deal with statement objects but are called from other modules, such as connection
// (conn.cpp)
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, int /*sql_len*/, bool direct, const char* _FN_ TSRMLS_DC )
class common_exec_error_handler {
public:
common_exec_error_handler( sqlsrv_stmt* stmt ) :
_stmt( stmt ), _success( false )
{
}
~common_exec_error_handler( void )
{
if( !_success ) {
_stmt->free_param_data();
SQLFreeStmt( _stmt->ctx.handle, SQL_RESET_PARAMS );
}
}
void successful( void )
{
_success = true;
}
private:
bool _success;
sqlsrv_stmt* _stmt;
};
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, int sql_len, bool direct, const char* _FN_ TSRMLS_DC )
{
SQLRETURN r;
SQLSMALLINT i;
common_exec_error_handler error_exit_handler( stmt );
// hold the buffers for UTF-16 encoded buffers bound as input parameters. These buffers are released by the zval
// upon function exit.
zval_auto_ptr wbuffer_allocs;
MAKE_STD_ZVAL( wbuffer_allocs );
array_init( wbuffer_allocs );
close_active_stream( stmt TSRMLS_CC );
if( stmt->executed ) {
@ -972,6 +1207,13 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
CHECK_SQL_ERROR_EX( r == SQL_ERROR, stmt, _FN_, NULL, return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
} while( r != SQL_NO_DATA );
// since we're done, clean up output parameters
bool converted = adjust_output_lengths_and_encodings( stmt, _FN_ TSRMLS_CC );
if( !converted ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
return false;
}
}
stmt->free_param_data();
@ -980,14 +1222,13 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
if( stmt->params_z ) {
HashTable* params_ht = Z_ARRVAL_P( stmt->params_z );
// 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<SQLINTEGER*>( emalloc( zend_hash_num_elements( params_ht ) * sizeof( SQLINTEGER )));
stmt->params_ind_ptr = static_cast<SQLINTEGER*>( sqlsrv_malloc( zend_hash_num_elements( params_ht ) * sizeof( SQLINTEGER )));
}
for( i = 1, zend_hash_internal_pointer_reset( params_ht );
@ -1003,6 +1244,7 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
SQLPOINTER buffer = NULL;
SQLUINTEGER buffer_len = 0;
sqlsrv_sqltype sql_type;
sqlsrv_phptype sqlsrv_phptype;
sql_type.typeinfo.type = SQL_BINARY;
sql_type.typeinfo.size = SQLSRV_INVALID_SIZE;
sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE;
@ -1017,27 +1259,32 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
if( Z_TYPE_P( param_z ) == IS_ARRAY ) {
param_z = parse_param_array( stmt, _FN_, param_z, i, direction, php_type, sql_c_type, sql_type,
column_size, decimal_digits TSRMLS_CC );
column_size, decimal_digits, sqlsrv_phptype TSRMLS_CC );
// an error occurred, so return false
if( param_z == NULL ) {
return false;
}
}
return false;
}
}
// otherwise use the defaults
else {
bool success = determine_param_defaults( stmt, _FN_, param_z, i, php_type, direction, sql_type,
sql_c_type, column_size, decimal_digits TSRMLS_CC );
sql_c_type, column_size, decimal_digits, sqlsrv_phptype TSRMLS_CC );
if( !success ) {
return false;
}
}
if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
}
// set the buffer and buffer_len
switch( php_type ) {
case IS_NULL:
CHECK_SQL_ERROR_EX( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT, stmt, _FN_,
SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; );
SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, return false; );
buffer = NULL;
buffer_len = 0;
stmt->params_ind_ptr[ i-1 ] = SQL_NULL_DATA;
@ -1055,30 +1302,77 @@ 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 );
// 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 )) {
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( direction == SQL_PARAM_INPUT && sqlsrv_phptype.typeinfo.encoding == CP_UTF8 ) {
zval_auto_ptr wbuffer_z;
ALLOC_INIT_ZVAL( wbuffer_z );
bool converted = convert_input_param_to_utf16( param_z, wbuffer_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;
}
// 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
buffer = Z_STRVAL_P( wbuffer_z );
buffer_len = Z_STRLEN_P( wbuffer_z );
// memory added here is released upon function exit
add_next_index_zval( wbuffer_allocs, wbuffer_z );
wbuffer_z.transferred();
}
// 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*>( erealloc( buffer, column_size + 1 ));
buffer_len = column_size + 1;
reinterpret_cast<char*>( buffer )[ column_size ] = '\0';
ZVAL_STRINGL( param_z, reinterpret_cast<char*>( buffer ), Z_STRLEN_P( param_z ), 0 );
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 );
}
// 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 );
// 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 in the statement
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 ) {
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 );
}
}
// 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;
@ -1096,30 +1390,39 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
if( zend_get_object_classname( param_z, &class_name, &class_name_len TSRMLS_CC ) == FAILURE ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, i );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return false;
}
if( class_name_len != DATETIME_CLASS_NAME_LEN || stricmp( class_name, DATETIME_CLASS_NAME )) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, i );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return false;
}
CHECK_SQL_ERROR_EX( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT, stmt, _FN_,
SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; );
SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, return false; );
// call the PHP function date_format to convert the object to a string that SQL Server understands
ALLOC_INIT_ZVAL( buffer_z );
ALLOC_INIT_ZVAL( function_z );
ALLOC_INIT_ZVAL( format_z );
ZVAL_STRINGL( function_z, "date_format", sizeof( "date_format" ) - 1, 1 );
ZVAL_STRINGL( format_z, const_cast<char*>( DATETIME_FORMAT ), DATETIME_FORMAT_LEN, 1 );
// if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
// sql type, it lacks the timezone. These conversions are only used when the specific type is specified
// by the user in the param array.
if( sql_type.typeinfo.type == SQL_SS_TIMESTAMPOFFSET ) {
ZVAL_STRINGL( format_z, const_cast<char*>( DATETIMEOFFSET_FORMAT ), DATETIMEOFFSET_FORMAT_LEN, 1 /* dup */ );
}
else if( sql_type.typeinfo.type == SQL_TYPE_DATE ) {
ZVAL_STRINGL( format_z, const_cast<char*>( DATE_FORMAT ), DATE_FORMAT_LEN, 1 /* dup */ );
}
else {
ZVAL_STRINGL( format_z, const_cast<char*>( DATETIME_FORMAT ), DATETIME_FORMAT_LEN, 1 /* dup */);
}
params[0] = param_z;
params[1] = format_z;
result = call_user_function( EG( function_table ), NULL, function_z, buffer_z, 2, params TSRMLS_CC );
if( result == FAILURE ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, i );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
zval_ptr_dtor( &buffer_z );
return false;
}
@ -1140,10 +1443,18 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
case IS_RESOURCE:
{
CHECK_SQL_ERROR_EX( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT, stmt, _FN_,
SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; );
php_stream* param_stream = NULL;
php_stream_from_zval_no_verify( param_stream, &param_z );
buffer = param_z;
SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, return false; );
if( stmt->param_streams == NULL ) {
ALLOC_INIT_ZVAL( stmt->param_streams );
int zr = array_init( stmt->param_streams );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false );
}
sqlsrv_stream_encoding stream_encoding( param_z, sqlsrv_phptype.typeinfo.encoding );
HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams );
int next_index = zend_hash_next_free_element( streams_ht );
int zr = zend_hash_index_update( streams_ht, next_index, &stream_encoding, sizeof( stream_encoding ), NULL );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false );
buffer = reinterpret_cast<SQLPOINTER>( next_index );
zval_add_ref( &param_z ); // so that it doesn't go away while we're using it
buffer_len = 0;
stmt->params_ind_ptr[ i-1 ] = SQL_DATA_AT_EXEC;
@ -1151,14 +1462,13 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
}
default:
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAM_TYPE TSRMLS_CC );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return false;
}
if( direction < 0 || direction > 0xffff ) DIE( "direction not valid SQLSMALLINT" );
r = SQLBindParameter( stmt->ctx.handle, i, static_cast<SQLSMALLINT>( direction ), sql_c_type, sql_type.typeinfo.type, column_size, decimal_digits,
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_ERROR( r, stmt, _FN_, NULL, return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
}
}
@ -1166,7 +1476,24 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
// execute the statement
if( direct ) {
if( sql_string == NULL ) DIE( "sqlsrv_stmt_common_execute: sql_string must be valid when direct = true");
r = SQLExecDirect( stmt->ctx.handle, const_cast<SQLCHAR*>( sql_string ), SQL_NTS /*sql_len*/ );
wchar_t* wsql_string;
unsigned int wsql_len = 0;
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
if( sql_len == 0 || ( sql_string[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( 1 ));
wsql_string[0] = '\0';
wsql_len = 0;
}
else {
wsql_string = utf16_string_from_mbcs_string( stmt->conn->default_encoding, reinterpret_cast<const char*>( sql_string ), sql_len, &wsql_len );
if( wsql_string == NULL ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
return false;
}
}
r = SQLExecDirectW( stmt->ctx.handle, const_cast<SQLWCHAR*>( wsql_string ), SQL_NTS );
sqlsrv_free( wsql_string );
}
else {
if( sql_string != NULL ) DIE( "sqlsrv_stmt_common_execute: sql_string must be NULL when direct = false");
@ -1186,30 +1513,52 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
}
}
}
// 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 ) {
// if no data was returned, then handle the output string parameters immediately
stmt->has_rows = false;
bool converted = adjust_output_lengths_and_encodings( stmt, _FN_ TSRMLS_CC );
if( !converted ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
return false;
}
}
// if results should have been returned, check for errors
else if( !SQL_SUCCEEDED( r )) {
SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLSMALLINT len;
SQLRETURN dr = SQLGetDiagField( SQL_HANDLE_STMT, stmt->ctx.handle, 1, SQL_DIAG_MESSAGE_TEXT, err_msg, SQL_MAX_MESSAGE_LENGTH, &len );
// before general error checking, we check for the 'connection busy' error caused by having MultipleActiveResultSets off
// and return a more helpful message prepended to the ODBC errors if that error occurs
if( SQL_SUCCEEDED( dr ) && len == sizeof( CONNECTION_BUSY_ODBC_ERROR ) - 1 &&
!strcmp( reinterpret_cast<const char*>( err_msg ), CONNECTION_BUSY_ODBC_ERROR )) {
handle_error( &stmt->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_MARS_OFF TSRMLS_CC );
return false;
}
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
}
else {
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; );
// this CHECK_SQL_ERROR is to return warnings as errors if that configuration setting is true
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, 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 )) {
stmt->has_rows = false;
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;
}
// check for no columns, which means that there are no rows
bool result_present;
r = has_any_result( stmt, result_present );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
if( !result_present ) {
// if there are no rows, then adjust the output parameters
bool adjusted = adjust_output_lengths_and_encodings( stmt, _FN_ TSRMLS_CC );
if( !adjusted ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
return false;
}
}
}
@ -1217,6 +1566,8 @@ bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, i
stmt->new_result_set();
stmt->executed = true;
error_exit_handler.successful();
return true;
}
@ -1233,112 +1584,59 @@ void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC )
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 );
sqlsrv_free( stmt->params_ind_ptr );
stmt->params_ind_ptr = NULL;
}
}
close_active_stream( stmt TSRMLS_CC );
if( stmt->param_buffer != NULL ) {
efree( stmt->param_buffer );
sqlsrv_free( stmt->param_buffer );
stmt->param_buffer = NULL;
}
stmt->free_param_data();
r = SQLFreeHandle( SQL_HANDLE_STMT, stmt->ctx.handle );
// 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 statement handle %1!d!", stmt->ctx.handle );
LOG( SEV_ERROR, LOG_STMT, "Failed to free handle for stmt resource %1!d!", stmt->conn_index );
}
// mark the statement as closed
stmt->ctx.handle = SQL_NULL_HANDLE;
}
void free_php_resources( zval* stmt_z TSRMLS_DC )
void free_stmt_resource( zval* stmt_z TSRMLS_DC )
{
sqlsrv_stmt* stmt = NULL;
stmt = static_cast<sqlsrv_stmt*>( zend_fetch_resource( &stmt_z TSRMLS_CC, -1, "sqlsrv_stmt", NULL, 1, sqlsrv_stmt::descriptor ));
if( stmt == NULL ) {
LOG( SEV_WARNING, current_log_subsystem, "Statement resource %1!d! already released", Z_RESVAL_P( stmt_z ));
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 %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 );
zval_ptr_dtor( &stmt_z );
}
void remove_from_connection( sqlsrv_stmt* stmt TSRMLS_DC )
{
if( stmt->ctx.handle != NULL ) DIE( "Statement ODBC resources not released prior to removing from the connection" );
// delete the entry in the connection's statement list
LOG( SEV_NOTICE, LOG_STMT, "Deleting statement index %1!d! from the connection", stmt->conn_index );
if( zend_hash_index_del( stmt->conn->stmts, stmt->conn_index ) == FAILURE ) {
DIE( "Couldn't delete statement index %d from the connection", stmt->conn_index );
}
// set the connection to released
stmt->conn = NULL;
}
// sqlsrv_stmt_hash_dtor
// called when the entry in the connection's list of statements is deleted
void sqlsrv_stmt_hash_dtor( void* stmt_ptr )
{
zval* stmt_z = *(static_cast<zval**>( stmt_ptr ));
if( Z_REFCOUNT_P( stmt_z ) <= 0 ) {
DIE( "Statement refcount should be > 0 when deleting from the connection's statement list" );
}
sqlsrv_stmt* stmt = NULL;
TSRMLS_FETCH();
stmt = static_cast<sqlsrv_stmt*>( zend_fetch_resource( &stmt_z TSRMLS_CC, -1, "sqlsrv_stmt", NULL, 1, sqlsrv_stmt::descriptor ));
if( !stmt )
return;
stmt->conn = NULL; // remove the connection so the statement resource destructor won't try to remove itself from the connection's list
if( stmt->ctx.handle != SQL_NULL_HANDLE ) {
free_odbc_resources( stmt TSRMLS_CC );
}
free_php_resources( stmt_z TSRMLS_CC );
}
void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC )
{
// get the structure
sqlsrv_stmt *stmt = static_cast<sqlsrv_stmt*>( rsrc->ptr );
LOG( SEV_NOTICE, LOG_STMT, "sqlsrv_stmt_dtor: entering" );
if( stmt->ctx.handle != SQL_NULL_HANDLE ) {
free_odbc_resources( stmt TSRMLS_CC );
}
free_odbc_resources( stmt TSRMLS_CC );
if( stmt->conn ) {
remove_from_connection( stmt TSRMLS_CC );
int zr = zend_hash_index_del( stmt->conn->stmts, stmt->conn_index );
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_STMT, "Failed to remove statement reference from the connection" );
}
}
sqlsrv_free( stmt );
rsrc->ptr = NULL;
}
// centralized place to release all the parameter data that accrues during the execution
@ -1346,9 +1644,9 @@ void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC )
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( &param_output_strings );
param_output_strings = NULL;
if( param_strings ) {
zval_ptr_dtor( &param_strings );
param_strings = NULL;
}
// if we allocated any datetime strings in a previous execution, release them here.
@ -1356,8 +1654,13 @@ void sqlsrv_stmt::free_param_data( void )
zval_ptr_dtor( &param_datetime_buffers );
param_datetime_buffers = NULL;
}
}
// if we allocated any streams in a previous execution, release them here.
if( param_streams ) {
zval_ptr_dtor( &param_streams );
param_streams = NULL;
}
}
// to be called whenever a new result set is created, such as after an
// execute or next_result. Resets the state variables.
@ -1366,40 +1669,31 @@ void sqlsrv_stmt::new_result_set( void )
fetch_called = false;
if( fetch_fields ) {
for( int i = 0; i < fetch_fields_count; ++i ) {
efree( fetch_fields[ i ].name );
sqlsrv_free( fetch_fields[ i ].name );
}
efree( fetch_fields );
sqlsrv_free( fetch_fields );
}
fetch_fields = NULL;
fetch_fields_count = 0;
last_field_index = -1;
past_fetch_end = false;
past_next_result_end = false;
has_rows = false;
}
// *** internal functions ***
namespace {
SQLRETURN has_rows( sqlsrv_stmt* stmt, bool& rows_present )
// loop through the output string parameters and adjust their lengths and their
// encoding from UTF-16 if necessary.
bool adjust_output_lengths_and_encodings( sqlsrv_stmt* stmt, const char* _FN_ TSRMLS_DC )
{
// 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 )
if( stmt->param_strings == NULL )
return true;
HashTable* params_ht = Z_ARRVAL_P( stmt->param_output_strings );
bool converted = true;
HashTable* params_ht = Z_ARRVAL_P( stmt->param_strings );
for( zend_hash_internal_pointer_reset( params_ht );
zend_hash_has_more_elements( params_ht ) == SUCCESS;
@ -1407,43 +1701,129 @@ bool adjust_output_string_lengths( sqlsrv_stmt* stmt, const char* _FN_ TSRMLS_DC
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; );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, zval_ptr_dtor( &stmt->param_strings ); return false; );
// 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 ];
ZVAL_STRINGL( output_string->string_z, str, str_len, 0 );
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;
}
}
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
}
}
zval_ptr_dtor( &stmt->param_output_strings );
stmt->param_output_strings = NULL;
zval_ptr_dtor( &stmt->param_strings );
stmt->param_strings = NULL;
return converted;
}
// utility routine to convert an input parameter from UTF-8 to UTF-16
bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z )
{
if( input_param_z != converted_param_z && Z_TYPE_P( converted_param_z ) != IS_NULL ) {
DIE( "convert_input_param_z called with unknown parameter state" );
}
const char* buffer = Z_STRVAL_P( input_param_z );
int buffer_len = Z_STRLEN_P( input_param_z );
int wchar_size;
// if the string is empty, then just return that the conversion succeeded as
// MultiByteToWideChar will "fail" on an empty string.
if( buffer_len == 0 ) {
ZVAL_STRINGL( converted_param_z, "", 0, 1 );
return true;
}
// if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string
wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), buffer_len, NULL, 0 );
// if there was a problem determining the size of the string, return false
if( wchar_size == 0 ) {
return false;
}
wchar_t* wbuffer = reinterpret_cast<wchar_t*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) ));
// convert the utf-8string to a wchar string in the new buffer
int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, (LPCSTR) buffer, buffer_len, wbuffer, wchar_size );
// if there was a problem converting the string, then return false
if( r == 0 ) {
sqlsrv_free( wbuffer );
return false;
}
wbuffer[ wchar_size ] = '\0';
ZVAL_STRINGL( converted_param_z, reinterpret_cast<char*>( wbuffer ),
wchar_size * sizeof( wchar_t ), 0 );
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.
bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt, __out zval* return_value TSRMLS_DC )
bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt, __out zval* return_value, const char* _FN_ TSRMLS_DC )
{
int stream_index = 0;
sqlsrv_stream_encoding* stream_encoding;
zval* param_z = NULL;
SQLRETURN r = SQL_SUCCESS;
HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams );
RETVAL_TRUE;
r = SQLParamData( stmt->ctx.handle, reinterpret_cast<SQLPOINTER*>( &param_z ));
r = SQLParamData( stmt->ctx.handle, reinterpret_cast<SQLPOINTER*>( &stream_index ));
int zr = zend_hash_index_find( streams_ht, stream_index, (void**) &stream_encoding );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETVAL_NULL(); return false; );
param_z = stream_encoding->stream_z;
// if there is a waiting parameter, make it current
if( r == SQL_NEED_DATA ) {
stmt->current_parameter = param_z;
stmt->current_parameter_read = 0;
stmt->current_stream = param_z;
stmt->current_stream_read = 0;
stmt->current_stream_encoding = stream_encoding->encoding;
}
// otherwise if it wasn't an error, we've exhausted the bound parameters, so return that we're done
else if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
CHECK_SQL_WARNING( r, stmt, "sqlsrv_send_stream_data", NULL );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
RETVAL_NULL();
return false;
}
// otherwise, record the error and return false
else {
CHECK_SQL_ERROR( r, stmt, "sqlsrv_send_stream_data", NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; );
}
// there are more parameters
@ -1452,8 +1832,8 @@ bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt, __out zval* ret
// get_field_common
// common code shared between sqlsrv_get_field and sqlsrv_fetch_array. The "return value" is transferred via the field_value
// parameter, FALSE being when an error occurs.
// common code shared between sqlsrv_get_field and sqlsrv_fetch_array. The "return value" is transferred via
// the field_value parameter, FALSE being when an error occurs.
void get_field_common( __inout sqlsrv_stmt* stmt, const char* _FN_, sqlsrv_phptype sqlsrv_phptype, SQLUSMALLINT field_index, __out zval**field_value TSRMLS_DC )
{
SQLRETURN r;
@ -1464,7 +1844,7 @@ void get_field_common( __inout sqlsrv_stmt* stmt, const char* _FN_, sqlsrv_phpty
CHECK_SQL_ERROR_EX( !stmt->fetch_called, stmt, _FN_, SQLSRV_ERROR_FETCH_NOT_CALLED, ZVAL_FALSE( *field_value ); return; );
// make sure they're not trying to retrieve fields incorrectly. Otherwise return a helpful sqlsrv error
if( stmt->last_field_index > field_index ) {
if( !stmt->scrollable && stmt->last_field_index > field_index ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_FIELD_INDEX_ERROR TSRMLS_CC, field_index, stmt->last_field_index );
ZVAL_FALSE( *field_value );
return;
@ -1476,33 +1856,29 @@ void get_field_common( __inout sqlsrv_stmt* stmt, const char* _FN_, sqlsrv_phpty
// call a refactored routine get_field_as_string
case SQLSRV_PHPTYPE_STRING:
{
SQLSMALLINT c_type = ( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_CHAR ) ? SQL_C_CHAR : SQL_C_BINARY;
get_field_as_string( stmt, c_type, field_index, *field_value, _FN_ TSRMLS_CC );
get_field_as_string( stmt, sqlsrv_phptype, field_index, *field_value, _FN_ TSRMLS_CC );
if( Z_TYPE_PP( field_value ) == IS_BOOL && Z_LVAL_PP( field_value ) == 0 ) {
return;
}
}
break;
// create a stream wrapper around the field and return that object to the PHP script. calls to fread on the stream
// will result in calls to SQLGetData. This is handled in stream.cpp. See that file for how these fields are used.
// create a stream wrapper around the field and return that object to the PHP script. calls to fread
// on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file
// for how these fields are used.
case SQLSRV_PHPTYPE_STREAM:
{
php_stream* stream;
sqlsrv_stream* ss;
SQLINTEGER sql_type;
SQLINTEGER sql_display_size;
r = SQLColAttribute( stmt->ctx.handle, field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
r = SQLColAttribute( stmt->ctx.handle, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &sql_display_size );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
CHECK_SQL_ERROR_EX( !is_streamable_type( sql_type ), stmt, _FN_, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, ZVAL_FALSE( *field_value ); return; );
stream = php_stream_open_wrapper( "sqlsrv://teststuff", "r", 0, NULL );
stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL );
CHECK_SQL_ERROR_EX( !stream, stmt, _FN_, SQLSRV_ERROR_STREAM_CREATE, ZVAL_FALSE( *field_value ); return; );
ss = static_cast<sqlsrv_stream*>( stream->abstract );
ss->stmt = stmt;
@ -1564,8 +1940,8 @@ void get_field_common( __inout sqlsrv_stmt* stmt, const char* _FN_, sqlsrv_phpty
break;
}
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and convert it to
// a DateTime object and return the created object
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and
// convert it to a DateTime object and return the created object
case SQLSRV_PHPTYPE_DATETIME:
{
SQLRETURN r;
@ -1635,14 +2011,19 @@ SQLSMALLINT determine_c_type( int php_type, int encoding )
break;
case IS_STRING:
case IS_RESOURCE:
if( encoding == SQLSRV_ENCODING_CHAR ) {
switch( encoding ) {
case SQLSRV_ENCODING_CHAR:
sql_c_type = SQL_C_CHAR;
}
else if( encoding == SQLSRV_ENCODING_BINARY ) {
break;
case SQLSRV_ENCODING_BINARY:
sql_c_type = SQL_C_BINARY;
}
else {
break;
case CP_UTF8:
sql_c_type = SQL_C_WCHAR;
break;
default:
sql_c_type = SQLTYPE_INVALID;
break;
}
break;
// it is assumed that an object is a DateTime since it's the only thing we support.
@ -1661,12 +2042,13 @@ SQLSMALLINT determine_c_type( int php_type, int encoding )
// returns the SQL type constant that matches the PHP type and encoding given
// SQLTYPE_INVALID is returned when an invalid type is given since no SQL constant matches
sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value )
sqlsrv_sqltype determine_sql_type( zval const* value, int encoding, SERVER_VERSION server_version )
{
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = SQLTYPE_INVALID;
sql_type.typeinfo.size = SQLSRV_INVALID_SIZE;
sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE;
int php_type = Z_TYPE_P( value );
switch( php_type ) {
@ -1682,11 +2064,19 @@ sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value
break;
case IS_RESOURCE:
case IS_STRING:
if( encoding == SQLSRV_ENCODING_CHAR ) {
switch( encoding ) {
case SQLSRV_ENCODING_CHAR:
sql_type.typeinfo.type = SQL_VARCHAR;
}
else {
break;
case SQLSRV_ENCODING_BINARY:
sql_type.typeinfo.type = SQL_VARBINARY;
break;
case CP_UTF8:
sql_type.typeinfo.type = SQL_WVARCHAR;
break;
default:
DIE( "Illegal encoding in determine_sql_type" );
break;
}
if( Z_STRLEN_P( value ) > SQL_SERVER_MAX_FIELD_SIZE ) {
sql_type.typeinfo.size = SQLSRV_SIZE_MAX_TYPE;
@ -1699,7 +2089,19 @@ sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value
// verification that it's a real DateTime object occurs in sqlsrv_common_execute.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
sql_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
// since these are the date types of the highest precision for their respective server versions
if( server_version <= SERVER_VERSION_2005 ) {
sql_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
sql_type.typeinfo.size = 23;
sql_type.typeinfo.scale = 3;
}
else {
sql_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET;
sql_type.typeinfo.size = 34;
sql_type.typeinfo.scale = 7;
}
break;
default:
// this comes from the user, so we can't assert here
@ -1711,7 +2113,7 @@ sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value
}
// given a SQL Server type, return a sqlsrv php type
sqlsrv_phptype determine_sqlsrv_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string )
sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string )
{
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
@ -1725,17 +2127,17 @@ sqlsrv_phptype determine_sqlsrv_php_type( SQLINTEGER sql_type, SQLUINTEGER size,
case SQL_NUMERIC:
case SQL_WCHAR:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
}
break;
case SQL_BIT:
@ -1762,19 +2164,28 @@ sqlsrv_phptype determine_sqlsrv_php_type( SQLINTEGER sql_type, SQLUINTEGER size,
case SQL_SS_XML:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
}
break;
case SQL_FLOAT:
case SQL_REAL:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
if( stmt->conn->date_as_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
default:
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
@ -1786,7 +2197,8 @@ sqlsrv_phptype determine_sqlsrv_php_type( SQLINTEGER sql_type, SQLUINTEGER size,
// 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
bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, __out SQLUINTEGER* column_size, __out SQLSMALLINT* decimal_digits )
// 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;
@ -1827,8 +2239,6 @@ bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, __out SQLUI
break;
case SQL_BINARY:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
*column_size = sqlsrv_type.typeinfo.size;
@ -1840,6 +2250,19 @@ bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, __out SQLUI
return false;
}
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*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;
}
break;
case SQL_DECIMAL:
case SQL_NUMERIC:
*column_size = sqlsrv_type.typeinfo.size;
@ -1850,9 +2273,23 @@ bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, __out SQLUI
return false;
}
break;
// this can represent one of three data types: smalldatetime, datetime, and datetime2
// we present the largest for the version and let SQL Server downsize it
case SQL_TYPE_TIMESTAMP:
*column_size = 23;
*decimal_digits = 3;
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
break;
case SQL_SS_TIMESTAMPOFFSET:
*column_size = 34;
*decimal_digits = 7;
break;
case SQL_TYPE_DATE:
*column_size = 10;
*decimal_digits = 0;
break;
case SQL_SS_TIME2:
*column_size = 16;
*decimal_digits = 7;
break;
default:
// an invalid sql type should have already been dealt with, so we assert here.
@ -1863,6 +2300,41 @@ bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, __out SQLUI
return true;
}
// return whether or not an execution returned a result set with
// columns or not in result_present. The actual return value is
// either success or an error determining the presence of rows.
SQLRETURN has_result_columns( sqlsrv_stmt* stmt, bool& result_present )
{
// Use SQLNumResultCols to determine if we have rows or not.
SQLRETURN r;
SQLSMALLINT num_cols;
r = SQLNumResultCols( stmt->ctx.handle, &num_cols );
result_present = (num_cols != 0);
return r;
}
// return if any result set or rows affected message is waiting
// to be consumed and moved over by sqlsrv_next_result. The return
// value is if an error occurred determining. Whether
// or not results are present that must be consumed is returned
// in the result_present paramter.
SQLRETURN has_any_result( sqlsrv_stmt* stmt, bool& result_present )
{
// Use SQLNumResultCols to determine if we have rows or not.
SQLRETURN r;
r = has_result_columns( stmt, result_present );
if( !SQL_SUCCEEDED( r ))
return r;
// use SQLRowCount to determine if there is a rows status waiting
SQLLEN rows_affected;
r = SQLRowCount( stmt->ctx.handle, &rows_affected );
result_present = (result_present) || (rows_affected != -1);
return r;
}
// return if the type is a valid sql server type not including
// size, precision or scale. Use determine_precision_and_scale for that.
bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type )
@ -1889,6 +2361,9 @@ bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type )
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
break;
default:
return false;
@ -1909,7 +2384,8 @@ bool is_valid_sqlsrv_phptype( sqlsrv_phptype type )
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
{
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR ) {
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
return true;
}
break;
@ -1944,8 +2420,8 @@ SQLSMALLINT binary_or_char_encoding( SQLSMALLINT c_type )
case SQL_C_BINARY:
return SQLSRV_ENCODING_BINARY;
// we return character encoding for LONG and DOUBLE as well since by default the encoding is always character,
// even though it won't mean anything for these data types.
// we return character encoding for LONG and DOUBLE as well since by default the encoding is always
// character, even though it won't mean anything for these data types.
case SQL_C_CHAR:
case SQL_C_LONG:
case SQL_C_DOUBLE:
@ -1978,6 +2454,19 @@ bool is_fixed_size_type( SQLINTEGER sql_type )
return true;
}
bool should_be_converted_from_utf16( SQLINTEGER sql_type )
{
switch( sql_type ) {
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
return false;
}
return true;
}
// internal function to release the active stream. Called by each main API function
// that will alter the statement and cancel any retrieval of data from a stream.
void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC )
@ -2000,37 +2489,153 @@ 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.
bool convert_string_from_utf16( sqlsrv_phptype sqlsrv_phptype, 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
// calculate the number of characters needed
enc_len = WideCharToMultiByte( sqlsrv_phptype.typeinfo.encoding, 0,
reinterpret_cast<LPCWSTR>( utf16_string ), utf16_len,
NULL, 0, NULL, NULL );
if( enc_len == 0 ) {
return false;
}
// 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,
reinterpret_cast<LPCWSTR>( utf16_string ), utf16_len,
enc_string, enc_len, NULL, NULL );
if( rc == 0 ) {
return false;
}
sqlsrv_free( utf16_string );
*string = enc_string;
len = enc_len - 1;
return true;
}
bool calc_string_size( sqlsrv_stmt const* s, SQLUSMALLINT field_index, SQLUINTEGER& size, const char* _FN_ TSRMLS_DC )
{
SQLRETURN r;
SQLINTEGER sql_type;
r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
CHECK_SQL_ERROR( r, s, _FN_, NULL, return false; );
CHECK_SQL_WARNING( r, s, _FN_, NULL );
switch( sql_type ) {
// for types that are fixed in size or for which the size is unknown, return the display size.
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_GUID:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_REAL:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_BINARY:
case SQL_CHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_SS_XML:
case SQL_SS_UDT:
case SQL_WLONGVARCHAR:
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size );
CHECK_SQL_ERROR( r, s, _FN_, NULL, return false );
CHECK_SQL_WARNING( r, s, _FN_, NULL );
return true;
// for wide char types for which the size is known, return the octet length instead, since it will include the
// the number of bytes necessary for the string, not just the characters
case SQL_WCHAR:
case SQL_WVARCHAR:
r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size );
CHECK_SQL_ERROR( r, s, _FN_, NULL, return false; );
CHECK_SQL_WARNING( r, s, _FN_, NULL );
return true;
break;
default:
return false;
}
}
// a refactoring since the clause in get_field_common was becoming too large.
void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT field_index, __out zval* return_value, const char* _FN_ TSRMLS_DC )
void get_field_as_string( sqlsrv_stmt const* s, sqlsrv_phptype sqlsrv_phptype, SQLUSMALLINT field_index, __out zval* return_value, const char* _FN_ TSRMLS_DC )
{
SQLRETURN r;
char* field;
SQLINTEGER field_len;
SQLUINTEGER sql_display_size;
SQLINTEGER sql_type;
int initial_field_len = INITIAL_FIELD_STRING_LEN + (( c_type == SQL_C_CHAR ) ? 1 : 0);
SQLSMALLINT c_type;
SQLSMALLINT extra;
if( c_type != SQL_C_CHAR && c_type != SQL_C_BINARY ) DIE( "get_field_as_string requires C type to be either SQL_C_CHAR or SQL_C_BINARY" );
if( sqlsrv_phptype.typeinfo.type != SQLSRV_PHPTYPE_STRING ) {
DIE( "type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
}
if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_phptype.typeinfo.encoding = s->conn->default_encoding;
}
// set the C type and account for null characters at the end of the data
switch( sqlsrv_phptype.typeinfo.encoding ) {
case CP_UTF8:
c_type = SQL_C_WCHAR;
extra = sizeof( SQLWCHAR );
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
extra = 0;
break;
default:
c_type = SQL_C_CHAR;
extra = sizeof( SQLCHAR );
break;
}
r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &sql_display_size );
CHECK_SQL_ERROR( r, s, _FN_, NULL, RETURN_FALSE; );
CHECK_SQL_WARNING( r, s, _FN_, NULL );
r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
CHECK_SQL_ERROR( r, s, _FN_, NULL, RETURN_FALSE; );
CHECK_SQL_WARNING( r, s, _FN_, NULL );
bool success = calc_string_size( s, field_index, sql_display_size, _FN_ TSRMLS_CC );
// errors already posted
if( !success ) {
RETURN_FALSE;
}
// if this is a large type, then read the first few bytes to get the actual length from SQLGetData
if( sql_display_size == 0 || sql_display_size == LONG_MAX || sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) {
field_len = initial_field_len;
field = static_cast<char*>( emalloc( field_len + 1 ));
r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field, field_len, &field_len );
field_len = INITIAL_FIELD_STRING_LEN;
field = static_cast<char*>( sqlsrv_malloc( field_len + extra + 1 ));
r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field, field_len + extra, &field_len );
if( field_len == SQL_NULL_DATA ) {
efree( field );
sqlsrv_free( field );
RETURN_NULL();
}
if( r == SQL_NO_DATA ) {
handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index );
efree( field );
sqlsrv_free( field );
RETURN_FALSE;
return;
}
@ -2044,14 +2649,14 @@ void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT
// we do a power of two increasing size allocation to retrieve all the contents
if( field_len == SQL_NO_TOTAL ) {
SQLINTEGER dummy_field_len;
field_len = initial_field_len - ((c_type == SQL_C_CHAR) ? 1 : 0);
field_len = INITIAL_FIELD_STRING_LEN;
do {
initial_field_len = field_len;
SQLINTEGER initial_field_len = field_len;
field_len *= 2;
field = static_cast<char*>( erealloc( field, field_len + 1 ));
field = static_cast<char*>( sqlsrv_realloc( field, field_len + extra + 1 ));
field_len -= initial_field_len;
r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field + initial_field_len,
field_len + ((c_type == SQL_C_CHAR ) ? 1 : 0), &dummy_field_len );
field_len + extra, &dummy_field_len );
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
// so we calculate the actual length of the string with that.
@ -2063,26 +2668,26 @@ void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT
SQLGetDiagField( SQL_HANDLE_STMT, s->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, 6, &len );
}
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; );
CHECK_SQL_ERROR( r, s, _FN_, NULL, sqlsrv_free( field ); RETURN_FALSE; );
}
else {
field = static_cast<char*>( erealloc( field, field_len + 1 ));
SQLINTEGER last_field_len;
field = static_cast<char*>( sqlsrv_realloc( field, field_len + extra + 1 ));
field_len -= INITIAL_FIELD_STRING_LEN;
r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field + INITIAL_FIELD_STRING_LEN,
field_len + (( c_type == SQL_C_CHAR ) ? 1 : 0), &field_len );
if( field_len == SQL_NULL_DATA ) {
efree( field );
field_len + extra, &last_field_len );
if( last_field_len == SQL_NULL_DATA ) {
sqlsrv_free( field );
RETURN_NULL();
}
if( r == SQL_NO_DATA ) {
handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index );
efree( field );
sqlsrv_free( field );
RETURN_FALSE;
return;
}
CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; );
CHECK_SQL_ERROR( r, s, _FN_, NULL, sqlsrv_free( field ); RETURN_FALSE; );
field_len += INITIAL_FIELD_STRING_LEN;
field[ field_len ] = '\0'; // NULL terminate the string
}
}
else {
@ -2090,44 +2695,69 @@ void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT
}
}
else {
CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; );
CHECK_SQL_ERROR( r, s, _FN_, NULL, sqlsrv_free( field ); RETURN_FALSE; );
}
if( c_type == SQL_C_WCHAR ) {
bool converted = convert_string_from_utf16( sqlsrv_phptype, &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 );
RETURN_FALSE;
}
}
}
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.
// only allow binary retrievals for char and binary types. All others get a string converted
// to the encoding type they asked for.
if( is_fixed_size_type( sql_type )) {
c_type = SQL_C_CHAR;
c_type = SQL_C_WCHAR;
extra = sizeof( WCHAR );
}
if( c_type == SQL_C_BINARY && ( sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR )) {
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); // include the null terminator
}
else {
if( c_type == SQL_C_CHAR ) {
++sql_display_size;
}
field = static_cast<char*>( emalloc( sql_display_size + 1 ));
else if( c_type == SQL_C_WCHAR ) {
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); // include the null terminator
}
field = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
// get the data
r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field, sql_display_size, &field_len );
if( field_len == SQL_NULL_DATA ) {
efree( field );
sqlsrv_free( field );
RETURN_NULL();
}
if( r == SQL_NO_DATA ) {
handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index );
efree( field );
sqlsrv_free( field );
RETURN_FALSE;
return;
}
CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; );
CHECK_SQL_ERROR( r, s, _FN_, NULL, sqlsrv_free( field ); RETURN_FALSE; );
CHECK_SQL_WARNING( r, s, _FN_, NULL );
if( c_type == SQL_C_WCHAR ) {
bool converted = convert_string_from_utf16( sqlsrv_phptype, &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 );
RETURN_FALSE;
}
}
}
else {
DIE( "Invalid sql_display_size" );
return; // to eliminate a warning
}
field[ field_len ] = '\0'; // prevent a warning in debug mode
ZVAL_STRINGL( return_value, field, field_len, 0 );
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
// operator to set add 1 to fill the null terminator
field[ field_len ] = '\0';
return;
}
@ -2181,8 +2811,8 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type )
}
// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the field.
// encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h)
// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the
// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h)
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type )
{
SQLSRV_UNUSED( return_value_used );
@ -2221,6 +2851,29 @@ void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type )
}
// verify an encoding given to type_and_encoding by looking through the list
// of standard encodings created at module initialization time
bool verify_encoding( const char* encoding_string, __out sqlsrv_phptype& phptype_encoding TSRMLS_DC )
{
for( zend_hash_internal_pointer_reset( SQLSRV_G( encodings ));
zend_hash_has_more_elements( SQLSRV_G( encodings )) == SUCCESS;
zend_hash_move_forward( SQLSRV_G( encodings ) ) ) {
sqlsrv_encoding* encoding;
int zr = zend_hash_get_current_data( SQLSRV_G( encodings ), (void**) &encoding );
if( zr == FAILURE ) {
DIE( "Fatal: Error retrieving encoding from encoding hash table." );
}
if( !stricmp( encoding_string, encoding->iana )) {
phptype_encoding.typeinfo.encoding = encoding->code_page;
return true;
}
}
return false;
}
// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters.
// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h)
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type )
@ -2229,30 +2882,27 @@ void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type )
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
char* encoding_param;
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype.typeinfo.type = type;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
int encoding_param_len = 0;
if( type != SQLSRV_PHPTYPE_STREAM && type != SQLSRV_PHPTYPE_STRING ) {
DIE( "Invalid type passed to type_and_encoding" );
}
char* encoding_param;
int encoding_param_len = 0;
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) {
return;
}
if( !strnicmp( encoding_param, "binary", sizeof( "binary" ) / sizeof(char))) {
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
// set the default encoding values to invalid so that
// if the encoding isn't validated, it will return the invalid setting.
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype.typeinfo.type = type;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
if( !verify_encoding( encoding_param, sqlsrv_phptype TSRMLS_CC )) {
LOG( SEV_ERROR, LOG_STMT, "Invalid encoding for php type." );
}
else if( !strnicmp( encoding_param, "char", sizeof( "char" ) / sizeof(char))) {
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
}
else {
LOG( SEV_ERROR, LOG_STMT, "Invalid encoding. Must be either \"binary\" (SQLSRV_ENC_BINARY) or \"char\" (SQLSRV_ENC_CHAR)" );
}
ZVAL_LONG( return_value, sqlsrv_phptype.value );
}
@ -2260,7 +2910,8 @@ void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type )
// fetch_common
// the common code shared between fetch_array and fetch_object. This returns a hash_table into return_value
// containing the fields either indexed by number and/or field name as determined by fetch_type.
void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return_value, const char* _FN_, bool allow_empty_field_names TSRMLS_DC )
void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, long fetch_style, long fetch_offset,
__out zval* return_value, const char* _FN_, bool allow_empty_field_names TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
int zr = SUCCESS;
@ -2274,6 +2925,9 @@ void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return
// make sure the statement has been executed
CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE );
CHECK_SQL_ERROR_EX( stmt->past_fetch_end, stmt, _FN_, SQLSRV_ERROR_FETCH_PAST_END, RETURN_FALSE );
// verify the fetch style
CHECK_SQL_ERROR_EX( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE,
stmt, _FN_, SQLSRV_ERROR_INVALID_FETCH_STYLE, RETURN_FALSE );
// get the numer of columns in the result set
r = SQLNumResultCols( stmt->ctx.handle, &num_cols );
@ -2286,19 +2940,27 @@ void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
// move to the next record
r = SQLFetch( stmt->ctx.handle );
// return a Zend NULL if we're at the end of the result set.
if( r == SQL_NO_DATA ) {
stmt->past_fetch_end = true;
RETURN_NULL();
// if the statement is scrollable or has rows and is not scrollable then don't skip calling
// SQLFetchScroll the first time sqlsrv_fetch_array or sqlsrv_fetch_object is called
if( stmt->scrollable || !( stmt->has_rows && !stmt->fetch_called )) {
// move to the next record
r = SQLFetchScroll( stmt->ctx.handle, static_cast<SQLSMALLINT>( fetch_style ), ( fetch_style == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 );
// return a Zend NULL if we're at the end of the result set.
if( r == SQL_NO_DATA ) {
// if this is a forward only cursor, mark that we've passed the end so future calls result in an error
if( !stmt->scrollable ) {
stmt->past_fetch_end = true;
}
RETURN_NULL();
}
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
}
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
// make it legal to retrieve fields
stmt->fetch_called = true;
stmt->last_field_index = -1;
stmt->has_rows = true; // since we made it his far, we must have at least one row
zval_auto_ptr fields;
MAKE_STD_ZVAL( fields );
@ -2311,14 +2973,14 @@ void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return
char* field_name_temp = static_cast<char*>( alloca( field_name_len_max ));
SQLSMALLINT field_name_len;
emalloc_auto_ptr<sqlsrv_fetch_field> field_names;
field_names = static_cast<sqlsrv_fetch_field*>( emalloc( num_cols * sizeof( sqlsrv_fetch_field )));
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field> field_names;
field_names = static_cast<sqlsrv_fetch_field*>( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field )));
for( SQLUSMALLINT f = 0; f < num_cols; ++f ) {
r = SQLColAttribute( stmt->ctx.handle, f + 1, SQL_DESC_NAME, field_name_temp, field_name_len_max, &field_name_len, &unused );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
field_names[ f ].name = static_cast<char*>( emalloc( field_name_len + 1 ));
field_names[ f ].name = static_cast<char*>( sqlsrv_malloc( field_name_len + 1 ));
memcpy( (void*) field_names[ f ].name, field_name_temp, field_name_len );
field_names[ f ].name[ field_name_len ] = '\0'; // null terminate the field name since SQLColAttribute doesn't.
field_names[ f ].len = field_name_len + 1;
@ -2348,7 +3010,7 @@ void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
// map from the SQL Server type to a PHP type for the field
sqlsrv_php_type = determine_sqlsrv_php_type( field_type, field_len, true );
sqlsrv_php_type = determine_sqlsrv_php_type( stmt, field_type, field_len, true );
if( sqlsrv_php_type.typeinfo.type == PHPTYPE_INVALID ) { DIE( "Couldn't understand type returned by ODBC" ); }
// get the value
@ -2384,6 +3046,53 @@ void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return
fields.transferred();
}
// determine if a query returned any rows of data. It does this by actually fetching the first row
// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful.
// The return value simply states whether or not if an error occurred during the determination.
// (All errors are posted here before returning.)
bool determine_stmt_has_rows( sqlsrv_stmt* stmt, const char* _FN_ TSRMLS_DC )
{
stmt->has_rows = false;
SQLRETURN r = SQL_SUCCESS;
// if the statement is scrollable, our work is easier though less performant. We simply
// fetch the first row, and then roll the cursor back to be prior to the first row
if( stmt->scrollable ) {
r = SQLFetchScroll( stmt->ctx.handle, SQL_FETCH_FIRST, 0 );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
r = SQLFetchScroll( stmt->ctx.handle, SQL_FETCH_ABSOLUTE, 0 );
if( r != SQL_NO_DATA ) DIE( "Should have scrolled the cursor to the beginning of the result set." );
return true;
}
}
// otherwise, we fetch the first row, but record that we did and then sqlsrv_fetch checks this
// flag and simply skips the first fetch, knowing it was already done. It records its own
// flags to know if it should fetch on subsequent calls.
else {
r = SQLFetch( stmt->ctx.handle );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
return true;
}
}
if( r == SQL_NO_DATA ) {
return true;
}
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, return false );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
return true; // there are no rows, but no errors occurred, so return true
}
// send a single packet from a stream parameter to the database using ODBC. This will also
// handle the transition between parameters. It returns true if it is not done sending,
// false if it is finished or an error occurred. return_value is what should be returned
@ -2395,20 +3104,43 @@ bool send_stream_packet( __inout sqlsrv_stmt* stmt, __out zval* return_value, ch
// if there no current parameter to process, get the next one
// (probably because this is the first call to sqlsrv_send_stream_data)
if( stmt->current_parameter == NULL ) {
if( check_for_next_stream_parameter( stmt, return_value TSRMLS_CC ) == false ) {
// done. return_value is already set
if( stmt->current_stream == NULL ) {
if( check_for_next_stream_parameter( stmt, return_value, _FN_ TSRMLS_CC ) == false ) {
// done with sending parameters, so see if there is a result set or not, and if not, adjust
// the output string parameters, otherwise see if there is at least one row.
// return_value is already set (unless changed below)
if( Z_TYPE_P( return_value ) == IS_BOOL && zend_is_true( return_value )) {
// check for no columns, which means that there are no rows
bool result_present;
r = has_result_columns( stmt, result_present );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETVAL_FALSE; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
if( !result_present ) {
// if there are no rows, then adjust the output parameters
bool adjusted = adjust_output_lengths_and_encodings( stmt, _FN_ TSRMLS_CC );
if( !adjusted ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
RETVAL_FALSE;
}
}
}
stmt->current_stream = NULL;
stmt->current_stream_read = 0;
stmt->current_stream_encoding = SQLSRV_ENCODING_CHAR;
return false;
}
}
// get the stream from the zval we bound
php_stream* param_stream = NULL;
php_stream_from_zval_no_verify( param_stream, &stmt->current_parameter );
php_stream_from_zval_no_verify( param_stream, &stmt->current_stream );
CHECK_SQL_ERROR_EX( param_stream == NULL, stmt, _FN_, SQLSRV_ERROR_ZEND_STREAM,
zval_ptr_dtor( &stmt->current_parameter );
stmt->current_parameter = NULL;
stmt->current_parameter_read = 0;
zval_ptr_dtor( &stmt->current_stream );
stmt->current_stream = NULL;
stmt->current_stream_read = 0;
stmt->current_stream_encoding = SQLSRV_ENCODING_CHAR;
SQLCancel( stmt->ctx.handle );
RETVAL_FALSE;
return false;);
@ -2416,24 +3148,95 @@ bool send_stream_packet( __inout sqlsrv_stmt* stmt, __out zval* return_value, ch
// if we're at the end, then release our current parameter
if( php_stream_eof( param_stream )) {
// if no data was actually sent prior, then send a NULL
if( stmt->current_parameter_read == 0 ) {
if( stmt->current_stream_read == 0 ) {
// send an empty string, which is what a 0 length does.
r = SQLPutData( stmt->ctx.handle, stmt->param_buffer, 0 );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
}
zval_ptr_dtor( &stmt->current_parameter );
stmt->current_parameter = NULL;
stmt->current_parameter_read = 0;
stmt->current_stream = NULL;
stmt->current_stream_read = 0;
stmt->current_stream_encoding = SQLSRV_ENCODING_CHAR;
}
// read the data from the stream, send it via SQLPutData and track how much we've sent.
else {
size_t read = php_stream_read( param_stream, static_cast<char*>( stmt->param_buffer ), stmt->param_buffer_size );
stmt->current_parameter_read += read;
size_t buffer_size = stmt->param_buffer_size - 3; // -3 to preserve enough space for a cut off UTF-8 character
size_t read = php_stream_read( param_stream, static_cast<char*>( stmt->param_buffer ), buffer_size );
stmt->current_stream_read += read;
if( read > 0 ) {
r = SQLPutData( stmt->ctx.handle, stmt->param_buffer, read );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
// if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
// twice.
// If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
// since all other MBCS supported by SQL Server are 2 byte maximum size.
if( stmt->current_stream_encoding == CP_UTF8 ) {
// the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a
// expansion of 2x the UTF-8 size.
wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
int wsize = MultiByteToWideChar( stmt->current_stream_encoding, MB_ERR_INVALID_CHARS, reinterpret_cast<char*>( stmt->param_buffer ),
read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t ));
if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) {
// this will calculate how many bytes were cut off from the last UTF-8 character and read that many more
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
char* last_char = static_cast<char*>( stmt->param_buffer ) + read - 1;
size_t need_to_read = 0;
// rewind until we are at the byte that starts the cut off character
while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) {
--last_char;
++need_to_read;
}
// determine how many bytes we need to read in based on the number of bytes in the character (# of high bits set)
// versus the number of bytes we've already read.
switch( *last_char & UTF8_NBYTESEQ_MASK ) {
case UTF8_2BYTESEQ_TAG1:
case UTF8_2BYTESEQ_TAG2:
need_to_read = 1 - need_to_read;
break;
case UTF8_3BYTESEQ_TAG:
need_to_read = 2 - need_to_read;
break;
case UTF8_4BYTESEQ_TAG:
need_to_read = 3 - need_to_read;
break;
default:
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION ) );
SQLCancel( stmt->ctx.handle );
RETVAL_FALSE;
return false;
break;
}
// read the missing bytes
size_t new_read = php_stream_read( param_stream, static_cast<char*>( stmt->param_buffer ) + read, need_to_read );
// if the bytes couldn't be read, then we return an error
if( new_read != need_to_read ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION ) );
SQLCancel( stmt->ctx.handle );
RETVAL_FALSE;
return false;
}
// try the conversion again with the complete character
wsize = MultiByteToWideChar( stmt->current_stream_encoding, MB_ERR_INVALID_CHARS, reinterpret_cast<char*>( stmt->param_buffer ),
read + new_read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t ));
// something else must be wrong if it failed
if( wsize == 0 ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
SQLCancel( stmt->ctx.handle );
RETVAL_FALSE;
return false;
}
}
r = SQLPutData( stmt->ctx.handle, wbuffer, wsize * sizeof( wchar_t ));
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
}
else {
r = SQLPutData( stmt->ctx.handle, stmt->param_buffer, read );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
}
}
}
@ -2441,8 +3244,8 @@ bool send_stream_packet( __inout sqlsrv_stmt* stmt, __out zval* return_value, ch
return true;
}
zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval* param_array, SQLSMALLINT param_num, __out int& direction, __out zend_uchar& php_type, SQLSMALLINT& sql_c_type, __out sqlsrv_sqltype& sql_type,
__out SQLUINTEGER& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC )
zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval* param_array, SQLSMALLINT param_num, __out int& direction, __out zend_uchar& php_type, SQLSMALLINT& sql_c_type,
__out sqlsrv_sqltype& sql_type, __out SQLUINTEGER& column_size, __out SQLSMALLINT& decimal_digits, __out sqlsrv_phptype& sqlsrv_phptype TSRMLS_DC )
{
zval** var_or_val;
zval** temp;
@ -2450,6 +3253,9 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
bool php_type_param_was_null = true;
bool sql_type_param_was_null = true;
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
// handle the array parameters that contain the value/var, direction, php_type, sql_type
zend_hash_internal_pointer_reset( Z_ARRVAL_P( param_array ) );
if( zend_hash_has_more_elements( Z_ARRVAL_P( param_array )) == FAILURE || zend_hash_get_current_data( Z_ARRVAL_P( param_array ), (void**) &var_or_val ) == FAILURE ) {
@ -2479,7 +3285,7 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
direction = SQL_PARAM_INPUT;
}
// use the PHP type given rather than the type built in, since this could be an output parameter.
// use the SQLSRV_PHPTYPE type given rather than the type built in, since this could be an output parameter.
// determine the C type to pass to SQLBindParameter
if( zend_hash_move_forward( Z_ARRVAL_P( param_array ) ) == SUCCESS &&
zend_hash_get_current_data( Z_ARRVAL_P( param_array ), (void**) &temp ) == SUCCESS &&
@ -2487,10 +3293,7 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
php_type_param_was_null = false;
sqlsrv_phptype sqlsrv_phptype;
int encoding;
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
if( Z_TYPE_PP( temp ) != IS_LONG ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
@ -2503,9 +3306,14 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
return NULL;
}
// make sure the enums are 1:1
SQLSRV_STATIC_ASSERT( MAX_SQLSRV_PHPTYPE == ( sizeof( sqlsrv_to_php_type ) / sizeof( zend_uchar )) );
php_type = sqlsrv_to_php_type[ sqlsrv_phptype.typeinfo.type - 1 ];
SQLSRV_STATIC_ASSERT( MAX_SQLSRV_PHPTYPE == ( sizeof( sqlsrv_to_zend_phptype ) / sizeof( zend_uchar )) );
php_type = sqlsrv_to_zend_phptype[ sqlsrv_phptype.typeinfo.type - 1 ];
encoding = sqlsrv_phptype.typeinfo.encoding;
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
// by the connection attribute CharacterSet
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->default_encoding;
}
sql_c_type = determine_c_type( php_type, encoding );
if( sql_c_type == SQLTYPE_INVALID ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num );
@ -2518,7 +3326,9 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
php_type_param_was_null = true;
php_type = Z_TYPE_PP( var_or_val );
sql_c_type = determine_c_type( php_type, SQLSRV_ENCODING_CHAR );
sqlsrv_phptype.typeinfo.type = zend_to_sqlsrv_phptype[ php_type ];
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
sql_c_type = determine_c_type( php_type, stmt->conn->default_encoding );
if( sql_c_type == SQLTYPE_INVALID ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
@ -2545,7 +3355,7 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return NULL;
}
if( determine_column_size_or_precision( sql_type, &column_size, &decimal_digits ) == false ) {
if( determine_column_size_or_precision( stmt, sql_type, &column_size, &decimal_digits ) == false ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PRECISION TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return NULL;
@ -2555,13 +3365,15 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
// (the default encoding really should be something in the INI)
else {
sql_type_param_was_null = true;
sql_type = determine_sql_type( php_type, binary_or_char_encoding( sql_c_type ), *var_or_val);
// TODO UTF-8: When the default encoding may be set, change this to use the default encoding rather than
// binary_or_char_encoding of the C type
sql_type = determine_sql_type( *var_or_val, sqlsrv_phptype.typeinfo.encoding, stmt->conn->server_version );
if( sql_type.typeinfo.type == SQLTYPE_INVALID ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return NULL;
}
if( determine_column_size_or_precision( sql_type, &column_size, &decimal_digits ) == false ) {
if( determine_column_size_or_precision( stmt, sql_type, &column_size, &decimal_digits ) == false ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PRECISION TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return NULL;
@ -2580,7 +3392,7 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
// otherwise, just set the defaults as if they had not given us a full parameter array but just put in null
bool success = determine_param_defaults( stmt, _FN_, *var_or_val, param_num, php_type, direction, sql_type,
sql_c_type, column_size, decimal_digits TSRMLS_CC );
sql_c_type, column_size, decimal_digits, sqlsrv_phptype TSRMLS_CC );
if( !success ) {
return NULL;
}
@ -2591,16 +3403,15 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
// sql server type, infer the php type from the sql server type.
if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) {
sqlsrv_phptype sqlsrv_php_type;
int encoding;
sqlsrv_php_type = determine_sqlsrv_php_type( sql_type.typeinfo.type, column_size, true );
sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type.typeinfo.type, column_size, true );
// we DIE here since everything should have been validated already and to return the user an error
// for our own logic error would be confusing/misleading.
if( sqlsrv_php_type.typeinfo.type == PHPTYPE_INVALID ) DIE( "An invalid php type was returned with (supposed) validated sql type and column_sze" );
if( sqlsrv_phptype.typeinfo.type == PHPTYPE_INVALID ) DIE( "An invalid php type was returned with (supposed) validated sql type and column_sze" );
SQLSRV_STATIC_ASSERT( MAX_SQLSRV_PHPTYPE == ( sizeof( sqlsrv_to_php_type ) / sizeof( zend_uchar )) );
php_type = sqlsrv_to_php_type[ sqlsrv_php_type.typeinfo.type - 1 ];
encoding = sqlsrv_php_type.typeinfo.encoding;
SQLSRV_STATIC_ASSERT( MAX_SQLSRV_PHPTYPE == ( sizeof( sqlsrv_to_zend_phptype ) / sizeof( zend_uchar )) );
php_type = sqlsrv_to_zend_phptype[ sqlsrv_phptype.typeinfo.type - 1 ];
encoding = sqlsrv_phptype.typeinfo.encoding;
sql_c_type = determine_c_type( php_type, encoding );
}
@ -2624,7 +3435,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 ) {
ZVAL_STRINGL( *var_or_val, static_cast<char*>( emalloc( column_size )), column_size, 0 /* don't dup the string */ );
ZVAL_STRINGL( *var_or_val, static_cast<char*>( sqlsrv_malloc( column_size )), column_size, 0 /* don't dup the string */ );
}
}
else {
@ -2640,24 +3451,28 @@ zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval*
// common code for when just a variable/value is given as a parameter or when NULL is given
bool determine_param_defaults( sqlsrv_stmt const* stmt, const char* _FN_, zval const* param_z, int param_num, __out zend_uchar& php_type, __out int& direction,
__out sqlsrv_sqltype& sql_type, __out SQLSMALLINT& sql_c_type, __out SQLUINTEGER& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC )
__out sqlsrv_sqltype& sql_type, __out SQLSMALLINT& sql_c_type, __out SQLUINTEGER& column_size, __out SQLSMALLINT& decimal_digits,
__out sqlsrv_phptype& sqlsrv_phptype TSRMLS_DC )
{
direction = SQL_PARAM_INPUT;
php_type = Z_TYPE_P( param_z );
sql_type = determine_sql_type( php_type, SQLSRV_ENCODING_CHAR, param_z );
sql_type = determine_sql_type( param_z, stmt->conn->default_encoding, stmt->conn->server_version );
sqlsrv_phptype.typeinfo.type = php_type;
sqlsrv_phptype.typeinfo.encoding = stmt->conn->default_encoding;
if( sql_type.typeinfo.type == SQLTYPE_INVALID ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return false;
}
sql_c_type = determine_c_type( php_type, SQLSRV_ENCODING_CHAR );
sql_c_type = determine_c_type( php_type, stmt->conn->default_encoding );
if( sql_c_type == SQLTYPE_INVALID ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return false;
}
if( determine_column_size_or_precision( sql_type, &column_size, &decimal_digits ) == false ) {
if( determine_column_size_or_precision( stmt, sql_type, &column_size, &decimal_digits ) == false ) {
handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PRECISION TSRMLS_CC, param_num );
SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS );
return false;

View file

@ -10,7 +10,7 @@
// as well the legacy LOB types: text, ntext, and image.
//
// 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.
//----------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
@ -42,7 +42,7 @@ int sqlsrv_stream_close( php_stream* stream, int TSRMLS_DC )
// there is no active stream
ss->stmt->active_stream = NULL;
efree( ss );
sqlsrv_free( ss );
stream->abstract = NULL;
return 0;
@ -54,9 +54,10 @@ int sqlsrv_stream_close( php_stream* stream, int TSRMLS_DC )
size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, size_t count TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
SQLINTEGER left_to_read = 0;
SQLINTEGER read = 0;
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSMALLINT c_type = SQL_C_CHAR;
char* get_data_buffer = buf;
LOG( SEV_NOTICE, LOG_STMT, "sqlsrv_stream_read: asking for %1!d! bytes from stmt %2!d!, field %3!d!", count, ss->stmt->ctx.handle, ss->field );
@ -66,31 +67,61 @@ size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, si
if( ss == NULL ) DIE( "sqlsrv_stream* ss is NULL. Shouldn't ever be NULL" );
if( ss->encoding == SQLSRV_ENCODING_CHAR ) {
c_type = SQL_C_CHAR;
}
else {
c_type = SQL_C_BINARY;
switch( ss->encoding ) {
case SQLSRV_ENCODING_CHAR:
c_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
break;
case CP_UTF8:
c_type = SQL_C_WCHAR;
count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes
break;
default:
DIE( "Uknown encoding type when reading from a stream" );
break;
}
// get the data
r = SQLGetData( ss->stmt->ctx.handle, ss->field + 1, c_type, buf, count, &left_to_read );
if( c_type == SQL_C_WCHAR ) {
if( count > PHP_STREAM_BUFFER_SIZE ) {
count = PHP_STREAM_BUFFER_SIZE;
}
// use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16
get_data_buffer = reinterpret_cast<char*>( ss->stmt->param_buffer );
}
r = SQLGetData( ss->stmt->ctx.handle, ss->field + 1, c_type, get_data_buffer, count, &read );
// if the stream returns either no data, NULL data, or returns data < than the count requested then
// we are at the "end of the stream" so we mark it
if( r == SQL_NO_DATA || left_to_read == SQL_NULL_DATA || ( left_to_read != SQL_NO_TOTAL && static_cast<size_t>( left_to_read ) <= count )) {
if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( read != SQL_NO_TOTAL && static_cast<size_t>( read ) <= count )) {
stream->eof = 1;
}
// 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 ),
read >> 1, buf, count, NULL, NULL );
if( w == 0 ) {
stream->eof = 1;
handle_error( &ss->stmt->ctx, LOG_STMT, "sqlsrv_stream_read",
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE TSRMLS_CC, get_last_error_message() );
return 0;
}
read = w;
}
// if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator
// if it's a character encoded field
// if it's not a binary encoded field
if( r == SQL_SUCCESS_WITH_INFO ) {
SQLRETURN r;
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
r = SQLGetDiagField( SQL_HANDLE_STMT, ss->stmt->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
if( is_truncated_warning( state ) || left_to_read == SQL_NO_TOTAL ) {
if( c_type == SQL_C_CHAR ) {
if( is_truncated_warning( state ) || read == SQL_NO_TOTAL ) {
if( c_type != SQL_C_BINARY ) {
--count;
}
return count;
@ -102,7 +133,7 @@ size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, si
#pragma warning( pop )
CHECK_SQL_WARNING( r, ss->stmt, "sqlsrv_stream_read", NULL );
return left_to_read;
return read;
}
// function table for stream operations. We only support reading and closing the stream
@ -126,7 +157,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.
php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper,
static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper,
__in char*, __in char* mode,
int options, __in char **,
php_stream_context*
@ -140,9 +171,9 @@ php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper,
SQLSRV_UNUSED( __php_stream_call_depth );
#endif
emalloc_auto_ptr<sqlsrv_stream> ss;
sqlsrv_malloc_auto_ptr<sqlsrv_stream> ss;
ss = static_cast<sqlsrv_stream*>( emalloc( sizeof( sqlsrv_stream )));
ss = static_cast<sqlsrv_stream*>( sqlsrv_malloc( sizeof( sqlsrv_stream )));
memset( ss, 0, sizeof( sqlsrv_stream ));
// check for valid options

View file

@ -53,9 +53,9 @@ BEGIN
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. © 1997-2008 The PHP Group. All rights reserved.\0"
VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. © 1997-2009 The PHP Group. All rights reserved.\0"
VALUE "CompanyName", "Microsoft Corp.\0"
VALUE "FileDescription", FILE_DESCRIPTION "\0"
VALUE "FileDescription", "Microsoft SQL Server Driver for PHP\0"
VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION)
VALUE "InternalName", FILE_NAME "\0"
VALUE "LegalCopyright", "© 2009 Microsoft Corp. All Rights Reserved.\0"

147
util.cpp
View file

@ -3,12 +3,12 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: Utility functions for the SQL Server Driver for PHP 1.0
// Contents: Utility functions used by both connection or statement functions
//
// Comments: Mostly error handling and some type handling
//
// 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.
//----------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
@ -32,8 +32,12 @@ const char SSPWARN[] = "01SSP";
const int LOG_MSG_SIZE = 2048;
char log_msg[ LOG_MSG_SIZE ];
// buffer use to hold a formatted error message returned by get_last_error_message
const int ERR_MSG_SIZE = 2048;
char err_msg[ ERR_MSG_SIZE ];
// internal error that says that FormatMessage failed
const char* internal_format_error = "An internal error occurred. FormatMessage failed writing an error message.";
const char internal_format_error[] = "An internal error occurred. FormatMessage failed writing an error message.";
// *** internal functions ***
bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chain, zval** ignored_chain, int log_severity, int log_subsystem,
@ -80,7 +84,7 @@ sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_EXECUTED[] = {
{ IMSSP, "The statement must be executed before results can be retrieved.", -11, false }
};
sqlsrv_error SQLSRV_ERROR_ALREADY_IN_TXN[] = {
{ IMSSP, "Cannot begin a transaction until the current transaction has been completed by calling either sqlsrv_commit or sqlsrv_rollback.", -12, false }
{ IMSSP, "Cannot begin a transaction until the current transaction has been completed by calling either sqlsrv_commit or sqlsrv_rollback.", -12, false }
};
sqlsrv_error SQLSRV_ERROR_NOT_IN_TXN[] = {
{ IMSSP, "A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or sqlsrv_rollback.", -13, false }
@ -110,7 +114,7 @@ sqlsrv_error SQLSRV_ERROR_SERVER_INFO[] = {
{ IMSSP, "An error occurred while retrieving the server information.", -21, false }
};
sqlsrv_error SQLSRV_ERROR_FETCH_PAST_END[] = {
{ IMSSP, "There are no more rows in the active result set.", -22, false }
{ IMSSP, "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved.", -22, false }
};
sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_PREPARED[] = {
{ IMSSP, "A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false }
@ -165,9 +169,53 @@ 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_INPUT_PARAM_ENCODING_TRANSLATE[] = {
{ IMSSP, "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true }
};
sqlsrv_error SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE[] = {
{ IMSSP, "An error occurred translating string for an output param to UTF-8: %1!s!", -41, true }
};
sqlsrv_error SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE[] = {
{ IMSSP, "An error occurred translating string for a field to UTF-8: %1!s!", -42, true }
};
sqlsrv_error SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE[] = {
{ IMSSP, "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true }
};
sqlsrv_error SQLSRV_ERROR_INVALID_CONN_ENCODING[] = {
{ IMSSP, "An invalid '%1!s!' encoding was specified in the CharacterSet connection option", -44, true }
};
sqlsrv_error SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE[] = {
{ IMSSP, "An error occurred translating the query string to UTF-16: %1!s!", -46, true }
};
sqlsrv_error SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE[] = {
{ IMSSP, "An error occurred translating the connection string to UTF-16: %1!s!", -47, true }
};
sqlsrv_error SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING[] = {
{ IMSSP, "The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, 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 }
{ 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 }
};
sqlsrv_error SQLSRV_ERROR_MARS_OFF[] = {
{ IMSSP, "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.", -49, false }
};
sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE[] = {
{ IMSSP, "This function only works with statements that have static or keyset scrollable cursors.", -50, false }
};
sqlsrv_error SQLSRV_ERROR_STATEMENT_SCROLLABLE[] = {
{ IMSSP, "This function only works with statements that are not scrollable.", -51, false }
};
sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_STYLE[] = {
{ IMSSP, "The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. Please use one of the SQLSRV_SCROLL constants.", -53, false }
};
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 }
};
@ -180,9 +228,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 }
{ 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] )
//
// Returns extended error and/or warning information about the last sqlsrv
@ -497,6 +547,58 @@ bool check_sqlsrv_warnings( bool condition, sqlsrv_context const* ctx, int log_s
return true;
}
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string. An empty string passed in will result as
// a failure since MBTWC returns 0 for both an empty string and failure
// to convert.
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string,
unsigned int mbcs_len, __out_bcount(utf16_len) wchar_t* utf16_out_string,
unsigned int utf16_len )
{
unsigned int win_encoding = CP_ACP;
switch( php_encoding ) {
case SQLSRV_ENCODING_CHAR:
win_encoding = CP_ACP;
break;
// this shouldn't ever be set
case SQLSRV_ENCODING_BINARY:
DIE( "Invalid encoding" );
break;
default:
win_encoding = php_encoding;
break;
}
unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len );
if( required_len == 0 ) {
return 0;
}
utf16_out_string[ required_len ] = '\0';
return required_len;
}
// thin wrapper around convert_string_from_default_encoding that handles
// allocation of the destination string. An empty string passed in returns
// failure since it's a failure case for convert_string_from_default_encoding.
wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, unsigned int mbcs_len, unsigned int* utf16_len )
{
*utf16_len = (mbcs_len + 1) * sizeof( wchar_t );
wchar_t* utf16_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( *utf16_len ));
*utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len,
utf16_string, *utf16_len );
if( *utf16_len == 0 ) {
// we preserve the error and reset it because sqlsrv_free resets the last error
DWORD last_error = GetLastError();
sqlsrv_free( utf16_string );
SetLastError( last_error );
return NULL;
}
return utf16_string;
}
// wrapper for errors around the common handle_errors_and_warnings
bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... )
{
@ -556,6 +658,27 @@ void write_to_log( unsigned int severity, unsigned int subsystem TSRMLS_DC, cons
va_end( args );
}
// return an error message for GetLastError using FormatMessage.
// this function returns the msg pointer so that it may be used within
// another function call such as handle_error
const char* get_last_error_message( DWORD last_error )
{
if( last_error == 0 ) {
last_error = GetLastError();
}
DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
err_msg, sizeof( err_msg ), NULL );
if( r == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( internal_format_error ) < sizeof( err_msg ));
std::copy( internal_format_error, internal_format_error + sizeof( internal_format_error ), err_msg );
}
return err_msg;
}
// *** internal function implementations ***
namespace {
@ -621,12 +744,12 @@ bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chai
// the break at the end of the loop assures that we don't get stuck here.
while( ssphp ) {
emalloc_auto_ptr<sqlsrv_error> ssphp_new;
emalloc_auto_ptr<const char> ssphp_new_message;
sqlsrv_malloc_auto_ptr<sqlsrv_error> ssphp_new;
sqlsrv_malloc_auto_ptr<const char> ssphp_new_message;
if( ssphp->format ) {
ssphp_new = static_cast<sqlsrv_error*>( emalloc( sizeof( sqlsrv_error )));
ssphp_new->native_message = ssphp_new_message = static_cast<char const*>( emalloc( SQL_MAX_MESSAGE_LENGTH ));
ssphp_new = static_cast<sqlsrv_error*>( sqlsrv_malloc( sizeof( sqlsrv_error )));
ssphp_new->native_message = ssphp_new_message = static_cast<char const*>( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH ));
ssphp_new->sqlstate = ssphp->sqlstate;
ssphp_new->native_code = ssphp->native_code;
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, const_cast<LPSTR>( ssphp->native_message ), 0, 0,

View file

@ -3,17 +3,17 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: Version resource information
// Contents: Routines that use connection handles
//
// 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.
//----------------------------------------------------------------------------------------------------------------------------------
#define VER_FILEVERSION_STR "1.0.0.0"
#define _FILEVERSION 1,0,0,0
#define VER_FILEVERSION_STR "1.1.0.0"
#define _FILEVERSION 1,1,0,0
#define SQLVERSION_MAJOR 1
#define SQLVERSION_MINOR 0
#define SQLVERSION_MINOR 1
#define SQLVERSION_MMDD 0
#define SQLVERSION_REVISION 0