SQL Server Driver for PHP 1.1 August 2009 CTP
This commit is contained in:
parent
c920eb693c
commit
273ea337e7
2
CREDITS
2
CREDITS
|
@ -1 +1 @@
|
|||
SQL Server 2005 Driver for PHP
|
||||
SQL Server Driver for PHP 1.1 August 2009 CTP
|
||||
|
|
15
README.TXT
15
README.TXT
|
@ -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
929
conn.cpp
|
@ -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( ¶ms_z, _FN_ TSRMLS_CC )) {
|
||||
free_odbc_resources( stmt TSRMLS_CC );
|
||||
RETURN_FALSE;
|
||||
}
|
||||
if( !mark_params_by_reference( ¶ms_z, _FN_ TSRMLS_CC )) {
|
||||
free_odbc_resources( stmt TSRMLS_CC );
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
stmt->params_z = params_z;
|
||||
|
||||
// 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**>( ¶m ), 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
|
||||
|
|
99
init.cpp
99
init.cpp
|
@ -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);
|
||||
|
|
533
php_sqlsrv.h
533
php_sqlsrv.h
|
@ -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
1523
stmt.cpp
|
@ -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( ¶m_z );
|
||||
buffer_len = Z_STRLEN_PP( ¶m_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( ¶m_z ); // we have a reference to the param in the statement
|
||||
zval_add_ref( ¶m_z ); // we have a reference to the param
|
||||
}
|
||||
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
|
||||
|
||||
buffer = Z_STRVAL_PP( ¶m_z );
|
||||
buffer_len = Z_STRLEN_PP( ¶m_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( ¶m_z );
|
||||
buffer_len = Z_STRLEN_PP( ¶m_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( ¶m_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, ¶m_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( ¶m_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( ¶m_output_strings );
|
||||
param_output_strings = NULL;
|
||||
if( param_strings ) {
|
||||
zval_ptr_dtor( ¶m_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( ¶m_datetime_buffers );
|
||||
param_datetime_buffers = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// if we allocated any streams in a previous execution, release them here.
|
||||
if( param_streams ) {
|
||||
zval_ptr_dtor( ¶m_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*>( ¶m_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;
|
||||
|
|
65
stream.cpp
65
stream.cpp
|
@ -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
|
||||
|
|
|
@ -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
147
util.cpp
|
@ -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,
|
||||
|
|
10
version.h
10
version.h
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue