#ifndef CORE_SQLSRV_H #define CORE_SQLSRV_H //--------------------------------------------------------------------------------------------------------------------------------- // File: core_sqlsrv.h // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // // Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, // and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- //********************************************************************************************************************************* // Includes //********************************************************************************************************************************* #ifdef SQL_WCHART_CONVERT #undef SQL_WCHART_CONVERT #endif #ifndef _WCHART_DEFINED #define _WCHART_DEFINED #endif #include "php.h" #include "php_globals.h" #include "php_ini.h" #include "ext/standard/php_standard.h" #include "ext/standard/info.h" #ifndef _WIN32 // !_WIN32 #include "FormattedPrint.h" #include "StringFunctions.h" #else #include "VersionHelpers.h" #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef PHP_WIN32 #define PHP_SQLSRV_API __declspec(dllexport) #else #define PHP_SQLSRV_API #endif // #define MultiByteToWideChar SystemLocale::ToUtf16 #define stricmp strcasecmp #define strnicmp strncasecmp #define strnlen_s(s) strnlen_s(s, INT_MAX) #ifndef _WIN32 #define GetLastError() errno #define SetLastError(err) errno=err typedef struct _OSVERSIONINFOA { DWORD dwOSVersionInfoSize; DWORD dwMajorVersion; DWORD dwMinorVersion; DWORD dwBuildNumber; DWORD dwPlatformId; CHAR szCSDVersion[128]; // Maintenance string for PSS usage } OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA; typedef OSVERSIONINFOA OSVERSIONINFO; #endif // !_WIN32 // OACR is an internal Microsoft static code analysis tool #if defined(OACR) #include OACR_WARNING_PUSH 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." ) OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) #else // define to eliminate static analysis hints in the code #define OACR_WARNING_SUPPRESS( warning, msg ) #endif extern "C" { #if defined(_MSC_VER) #pragma warning(push) #pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) #endif #ifdef ZTS #include "TSRM.h" #endif #if _MSC_VER >= 1400 // typedef and macro to prevent a conflict between php.h and ws2tcpip.h. // php.h defines this constant as unsigned int which causes a compile error // in ws2tcpip.h. Fortunately php.h allows an override by defining // HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define // socklen_t here and override the php.h version. typedef int socklen_t; #define HAVE_SOCKLEN_T #endif #if defined(_MSC_VER) #pragma warning(pop) #endif #if ZEND_DEBUG // debug build causes warning C4505 to pop up from the Zend header files #pragma warning( disable: 4505 ) #endif } // extern "C" #if defined(OACR) OACR_WARNING_POP #endif #ifdef _WIN32 #include #include #endif // _WIN32 #if !defined(SQL_GUID) // imported from sqlext.h #define SQL_GUID (-11) #endif #if !defined(WC_ERR_INVALID_CHARS) // imported from winnls.h as it isn't included by 5.3.0 #define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars #endif // PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when // we use std::copy, which causes compilation to fail since we compile with warnings as errors. #if defined(ZEND_DEBUG) && defined(inline) #undef inline #endif #include #include #include #include #include #include #include #include // included for SQL Server specific constants #include "msodbcsql.h" #ifdef _WIN32 #include #endif // _WIN32 //********************************************************************************************************************************* // Constants and Types //********************************************************************************************************************************* // constants for maximums in SQL Server const int SS_MAXCOLNAMELEN = 128; const int SQL_SERVER_MAX_FIELD_SIZE = 8000; const int SQL_SERVER_MAX_PRECISION = 38; const int SQL_SERVER_MAX_TYPE_SIZE = 0; const int SQL_SERVER_MAX_PARAMS = 2100; const int SQL_SERVER_MAX_MONEY_SCALE = 4; // increase the maximum message length to accommodate for the long error returned for operand type clash // or for conversion of a long string const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2; // max size of a date time string when converting from a DateTime object to a string const int MAX_DATETIME_STRING_LEN = 256; // precision and scale for the date time types between servers const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; namespace AzureADOptions { const char AZURE_AUTH_SQL_PASSWORD[] = "SqlPassword"; const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword"; const char AZURE_AUTH_AD_MSI[] = "ActiveDirectoryMsi"; } // the message returned by ODBC Driver for SQL Server const char ODBC_CONNECTION_BUSY_ERROR[] = "Connection is busy with results for another command"; // 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 SQLSRV_PHPTYPE_NULL = 1, SQLSRV_PHPTYPE_INT, SQLSRV_PHPTYPE_FLOAT, SQLSRV_PHPTYPE_STRING, SQLSRV_PHPTYPE_DATETIME, SQLSRV_PHPTYPE_STREAM, MAX_SQLSRV_PHPTYPE, // highest value for a php type SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid }; // encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting // information as a string or a stream. enum SQLSRV_ENCODING { SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection 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_UTF8 = CP_UTF8, }; // the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. enum SQLSRV_FETCH_TYPE { MIN_SQLSRV_FETCH = 1, // lowest value for fetch type SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys MAX_SQLSRV_FETCH = 3, // highest value for fetch type }; // buffer size of a sql state (including the null character) const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; // default value of decimal places (no formatting required) const short NO_CHANGE_DECIMAL_PLACES = -1; // 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; // SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. // The size is the column size or precision, and scale is the decimal digits for precise numeric types. union sqlsrv_sqltype { struct typeinfo_t { int type:9; int size:14; int scale:8; } typeinfo; zend_long value; }; // SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) // and the encoding for strings and streams (see SQLSRV_ENCODING) union sqlsrv_phptype { struct typeinfo_t { unsigned type:8; unsigned encoding:16; } typeinfo; zend_long value; }; // static assert for enforcing compile time conditions template struct sqlsrv_static_assert; template <> struct sqlsrv_static_assert { _In_ static const int value = 1; }; #define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) //********************************************************************************************************************************* // Logging //********************************************************************************************************************************* // log_callback // a driver specific callback for logging messages // severity - severity of the message: notice, warning, or error // msg - the message to log in a FormatMessage style formatting // print_args - args to the message typedef void (*log_callback)( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, _In_opt_ va_list* print_args ); // each driver must register a log callback. This should be the first thing a driver does. void core_sqlsrv_register_logger( _In_ log_callback ); // a simple wrapper around a PHP error logging function. void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ... ); // a macro to make it convenient to use the function. #define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, ## __VA_ARGS__ ) // mask for filtering which severities are written to the log enum logging_severity { SEV_ERROR = 0x01, SEV_WARNING = 0x02, SEV_NOTICE = 0x04, SEV_ALL = -1, }; // Kill the PHP process and log the message to PHP void die( _In_opt_ const char* msg, ... ); #define DIE( msg, ... ) { die( msg, ## __VA_ARGS__ ); } //********************************************************************************************************************************* // Resource/Memory Management //********************************************************************************************************************************* // the macro max is defined and overrides the call to max in the allocator class #pragma push_macro( "max" ) #undef max // 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 ) inline void* sqlsrv_malloc_trace( _In_ size_t size, _In_ const char* file, _In_ int line ) { void* ptr = emalloc( size ); LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); return ptr; } inline void* sqlsrv_malloc_trace( _In_ size_t element_count, _In_ size_t element_size, _In_ size_t extra, _In_ const char* file, _In_ int line ) { OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); if(( element_count > 0 && element_size > 0 ) && ( element_count > element_size * element_count || element_size > element_size * element_count )) { DIE( "Integer overflow in sqlsrv_malloc" ); } if( element_size * element_count > element_size * element_count + extra ) { DIE( "Integer overflow in sqlsrv_malloc" ); } if( element_size * element_count + extra == 0 ) { DIE( "Allocation size must be more than 0" ); } void* ptr = emalloc( element_size * element_count + extra ); LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); return ptr; } inline void* sqlsrv_realloc_trace( void* buffer, _In_ size_t size, _In_ const char* file, _In_ int line ) { void* ptr = erealloc( original, size ); LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); return ptr; } inline void sqlsrv_free_trace( _Inout_ void* ptr, _In_ const char* file, _In_ int line ) { LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); efree( ptr ); } #define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) #define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) #define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) #define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) #else inline void* sqlsrv_malloc( _In_ size_t size ) { return emalloc( size ); } inline void* sqlsrv_malloc( _In_ size_t element_count, _In_ size_t element_size, _In_ size_t extra ) { OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); if(( element_count > 0 && element_size > 0 ) && ( element_count > element_size * element_count || element_size > element_size * element_count )) { DIE( "Integer overflow in sqlsrv_malloc" ); } if( element_size * element_count > element_size * element_count + extra ) { DIE( "Integer overflow in sqlsrv_malloc" ); } if( element_size * element_count + extra == 0 ) { DIE( "Allocation size must be more than 0" ); } return emalloc( element_size * element_count + extra ); } inline void* sqlsrv_realloc( _Inout_ void* buffer, _In_ size_t size ) { return erealloc( buffer, size ); } inline void sqlsrv_free( _Inout_ void* ptr ) { efree( ptr ); } #endif // trait class that allows us to assign const types to an auto_ptr template struct remove_const { typedef T type; }; template struct remove_const { typedef T* type; }; // allocator that uses the zend memory manager to manage memory // this allows us to use STL classes that still work with Zend objects template struct sqlsrv_allocator { // typedefs used by the STL classes typedef T value_type; typedef value_type* pointer; typedef const value_type* const_pointer; typedef value_type& reference; typedef const value_type& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; // conversion typedef (used by list and other STL classes) template struct rebind { typedef sqlsrv_allocator other; }; inline sqlsrv_allocator() {} inline ~sqlsrv_allocator() {} inline sqlsrv_allocator( sqlsrv_allocator const& ) {} template inline sqlsrv_allocator( sqlsrv_allocator const& ) {} // address (doesn't work if the class defines operator&) inline pointer address( _In_ reference r ) { return &r; } inline const_pointer address( _In_ const_reference r ) { return &r; } // memory allocation/deallocation inline pointer allocate( _In_ size_type cnt, typename std::allocator::const_pointer = 0 ) { return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); } inline void deallocate( _Inout_ pointer p, size_type ) { sqlsrv_free(p); } // size inline size_type max_size( void ) const { return std::numeric_limits::max() / sizeof(T); } // object construction/destruction inline void construct( _In_ pointer p, _In_ const T& t ) { new(p) T(t); } inline void destroy( _Inout_ pointer p ) { p->~T(); } // equality operators inline bool operator==( sqlsrv_allocator const& ) { return true; } inline bool operator!=( _In_ sqlsrv_allocator const& a ) { return !operator==(a); } }; // base class for auto_ptrs that we define below. It provides common operators and functions // used by all the classes. template class sqlsrv_auto_ptr { public: sqlsrv_auto_ptr( void ) : _ptr( NULL ) { } ~sqlsrv_auto_ptr( void ) { static_cast(this)->reset( NULL ); } // call when ownership is transferred void transferred( void ) { _ptr = NULL; } // explicit function to get the pointer. T* get( void ) const { return _ptr; } // cast operator to allow auto_ptr to be used where a normal const * can be. operator const T* () const { return _ptr; } // cast operator to allow auto_ptr to be used where a normal pointer can be. operator typename remove_const::type () const { return _ptr; } operator bool() const { return _ptr != NULL; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ int index ) const { return _ptr[index]; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ unsigned int index ) const { return _ptr[index]; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ long index ) const { return _ptr[index]; } #ifdef __WIN64 // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ std::size_t index ) const { return _ptr[index]; } #endif // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ unsigned short index ) const { return _ptr[index]; } // access elements of a structure through the auto ptr T* const operator->( void ) const { return _ptr; } // value from reference operator (i.e., i = *(&i); or *i = blah;) T& operator*() const { 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( _In_opt_ T* ptr ) : _ptr( ptr ) { } sqlsrv_auto_ptr( _Inout_opt_ sqlsrv_auto_ptr& src ) { if( _ptr ) { static_cast(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=( _In_opt_ T* ptr ) { static_cast( this )->reset( ptr ); return ptr; } T* _ptr; }; // 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". // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. template class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { public: sqlsrv_malloc_auto_ptr( void ) : sqlsrv_auto_ptr >( NULL ) { } sqlsrv_malloc_auto_ptr( _Inout_opt_ const sqlsrv_malloc_auto_ptr& src ) : sqlsrv_auto_ptr >( src ) { } // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. void reset( _In_opt_ T* ptr = NULL ) { if( sqlsrv_auto_ptr >::_ptr ) sqlsrv_free( (void*) sqlsrv_auto_ptr >::_ptr ); sqlsrv_auto_ptr >::_ptr = ptr; } T* operator=( _In_opt_ T* ptr ) { return sqlsrv_auto_ptr >::operator=( ptr ); } void operator=( _Inout_opt_ sqlsrv_malloc_auto_ptr& src ) { T* p = src.get(); src.transferred(); this->_ptr = p; } // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer void resize( _In_ size_t new_size ) { sqlsrv_auto_ptr >::_ptr = reinterpret_cast( sqlsrv_realloc( sqlsrv_auto_ptr >::_ptr, new_size )); } }; // auto ptr for Zend hash tables. Used to clean up a hash table allocated when // something caused an early exit from the function. This is used when the hash_table is // allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. class hash_auto_ptr : public sqlsrv_auto_ptr { public: hash_auto_ptr( void ) : sqlsrv_auto_ptr( NULL ) { } // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. void reset( _In_opt_ HashTable* ptr = NULL ) { if (_ptr != NULL) { zend_hash_destroy(_ptr); FREE_HASHTABLE(_ptr); } _ptr = ptr; } HashTable* operator=( _In_opt_ HashTable* ptr ) { return sqlsrv_auto_ptr::operator=( ptr ); } private: hash_auto_ptr( _In_ HashTable const& hash ); hash_auto_ptr( _In_ hash_auto_ptr const& hash ); }; // an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. // zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed // (out of scope) or ownership is transferred using the function "transferred". class zval_auto_ptr : public sqlsrv_auto_ptr { public: zval_auto_ptr( void ) { } // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. void reset( _In_opt_ zval* ptr = NULL ) { if( _ptr ) zval_ptr_dtor(_ptr ); _ptr = ptr; } zval* operator=( _In_opt_ zval* ptr ) { return sqlsrv_auto_ptr::operator=( ptr ); } private: zval_auto_ptr( _In_ const zval_auto_ptr& src ); }; #pragma pop_macro( "max" ) //********************************************************************************************************************************* // sqlsrv_error //********************************************************************************************************************************* // *** PHP specific errors *** // sqlsrv errors are held in a structure of this type used by the driver handle_error functions // format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage // into the error message before returning it. // base class which can be instatiated with aggregates (see error constants) struct sqlsrv_error_const { SQLCHAR* sqlstate; SQLCHAR* native_message; SQLINTEGER native_code; bool format; }; // subclass which is used by the core layer to instantiate ODBC errors struct sqlsrv_error : public sqlsrv_error_const { sqlsrv_error( void ) { sqlstate = NULL; native_message = NULL; native_code = -1; format = false; } sqlsrv_error( _In_ SQLCHAR* sql_state, _In_ SQLCHAR* message, _In_ SQLINTEGER code, _In_ bool printf_format = false ) { sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_ERROR_MESSAGE_LENGTH + 1 )); strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); strcpy_s( reinterpret_cast( native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast( message )); native_code = code; format = printf_format; } sqlsrv_error( _In_ sqlsrv_error_const const& prototype ) { sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); } ~sqlsrv_error( void ) { if( sqlstate != NULL ) { sqlsrv_free( sqlstate ); } if( native_message != NULL ) { sqlsrv_free( native_message ); } } }; // an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { public: sqlsrv_error_auto_ptr( void ) : sqlsrv_auto_ptr( NULL ) { } sqlsrv_error_auto_ptr( _Inout_opt_ sqlsrv_error_auto_ptr const& src ) : sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) { } // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. void reset( _In_opt_ sqlsrv_error* ptr = NULL ) { if( _ptr ) { _ptr->~sqlsrv_error(); sqlsrv_free( (void*) _ptr ); } _ptr = ptr; } sqlsrv_error* operator=( _In_opt_ sqlsrv_error* ptr ) { return sqlsrv_auto_ptr::operator=( ptr ); } // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much // sense. Only the last one would have anything in it. void operator=( _Inout_opt_ sqlsrv_error_auto_ptr& src ) { sqlsrv_error* p = src.get(); src.transferred(); reset( p ); } }; //********************************************************************************************************************************* // Context //********************************************************************************************************************************* class sqlsrv_context; struct sqlsrv_conn; // error_callback // a driver specific callback for processing errors. // ctx - the context holding the handles // sqlsrv_error_code - specific error code to return. typedef bool (*error_callback)( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool error TSRMLS_DC, _In_opt_ va_list* print_args ); // sqlsrv_context // a context holds relevant information to be passed with a connection and statement objects. class sqlsrv_context { public: sqlsrv_context( _In_opt_ SQLSMALLINT type, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : handle_( SQL_NULL_HANDLE ), handle_type_( type ), name_( NULL ), err_( e ), driver_( drv ), last_error_(), encoding_( encoding ) { } sqlsrv_context( _In_ SQLHANDLE h, _In_opt_ SQLSMALLINT t, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : handle_( h ), handle_type_( t ), name_( NULL ), err_( e ), driver_( drv ), last_error_(), encoding_( encoding ) { } sqlsrv_context( _In_ sqlsrv_context const& ctx ) : handle_( ctx.handle_ ), handle_type_( ctx.handle_type_ ), name_( ctx.name_ ), err_( ctx.err_ ), driver_( ctx.driver_ ), last_error_( ctx.last_error_ ) { } virtual ~sqlsrv_context() { } void set_func( _In_z_ const char* f ) { name_ = f; } void set_last_error( _In_ sqlsrv_error_auto_ptr& last_error ) { last_error_ = last_error; } sqlsrv_error_auto_ptr& last_error( void ) { return last_error_; } // since the primary responsibility of a context is to hold an ODBC handle, we // provide these convenience operators for using them interchangeably operator SQLHANDLE ( void ) const { return handle_; } error_callback error_handler( void ) const { return err_; } SQLHANDLE handle( void ) const { return handle_; } SQLSMALLINT handle_type( void ) const { return handle_type_; } const char* func( void ) const { return name_; } void* driver( void ) const { return driver_; } void set_driver( _In_ void* driver ) { this->driver_ = driver; } void invalidate( void ) { if( handle_ != SQL_NULL_HANDLE ) { ::SQLFreeHandle( handle_type_, handle_ ); last_error_.reset(); } handle_ = SQL_NULL_HANDLE; } bool valid( void ) { return handle_ != SQL_NULL_HANDLE; } SQLSRV_ENCODING encoding( void ) const { return encoding_; } void set_encoding( _In_ SQLSRV_ENCODING e ) { encoding_ = e; } private: SQLHANDLE handle_; // ODBC handle for this context SQLSMALLINT handle_type_; // type of the ODBC handle const char* name_; // function name currently executing this context error_callback err_; // driver error callback if error occurs in core layer void* driver_; // points back to the driver for PDO sqlsrv_error_auto_ptr last_error_; // last error that happened on this object SQLSRV_ENCODING encoding_; // encoding of the context }; const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista // maps an IANA encoding to a code page struct sqlsrv_encoding { const char* iana; size_t iana_len; unsigned int code_page; bool not_for_connection; sqlsrv_encoding( _In_ const char* iana, _In_ unsigned int code_page, _In_ bool not_for_conn = false ): iana( iana ), iana_len( strnlen_s( iana )), code_page( code_page ), not_for_connection( not_for_conn ) { } }; //********************************************************************************************************************************* // Initialization //********************************************************************************************************************************* // variables set during initialization extern bool isVistaOrGreater; // used to determine if OS is Vista or Greater extern HashTable* g_encodings; // encodings supported by this driver void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func TSRMLS_DC ); void core_sqlsrv_mshutdown( _Inout_ sqlsrv_context& henv_cp, _Inout_ sqlsrv_context& henv_ncp ); // environment context used by sqlsrv_connect for when a connection error occurs. struct sqlsrv_henv { sqlsrv_context ctx; sqlsrv_henv( _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv ) : ctx( handle, SQL_HANDLE_ENV, e, drv ) { } }; //********************************************************************************************************************************* // Connection //********************************************************************************************************************************* // 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 }; // supported driver versions. // the latest RTWed ODBC is the first one enum DRIVER_VERSION { ODBC_DRIVER_UNKNOWN = -1, FIRST = 0, ODBC_DRIVER_17 = FIRST, ODBC_DRIVER_13 = 1, ODBC_DRIVER_11 = 2, LAST = ODBC_DRIVER_11 }; // forward decl struct sqlsrv_stmt; struct stmt_option; // This holds the various details of column encryption. struct col_encryption_option { bool enabled; // column encryption enabled, false by default SQLINTEGER akv_mode; sqlsrv_malloc_auto_ptr akv_id; sqlsrv_malloc_auto_ptr akv_secret; bool akv_required; col_encryption_option() : enabled( false ), akv_mode(-1), akv_required( false ) { } void akv_reset() { akv_id.reset(); akv_secret.reset(); akv_required = false; akv_mode = -1; } }; // *** connection resource structure *** // this is the resource structure returned when a connection is made. struct sqlsrv_conn : public sqlsrv_context { // instance variables SERVER_VERSION server_version; // version of the server that we're connected to col_encryption_option ce_option; // holds the details of what are required to enable column encryption DRIVER_VERSION driver_version; // version of ODBC driver sqlsrv_malloc_auto_ptr azure_ad_access_token; // initialize with default values sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) { server_version = SERVER_VERSION_UNKNOWN; driver_version = ODBC_DRIVER_UNKNOWN; } // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating // the sqlsrv_conn with a connection. }; enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_INVALID, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, SQLSRV_STMT_OPTION_SCROLLABLE, SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, SQLSRV_STMT_OPTION_DATE_AS_STRING, SQLSRV_STMT_OPTION_FORMAT_DECIMALS, SQLSRV_STMT_OPTION_DECIMAL_PLACES, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, }; namespace ODBCConnOptions { const char APP[] = "APP"; const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; const char Driver[] = "Driver"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; const char Database[] = "Database"; const char Encrypt[] = "Encrypt"; const char Failover_Partner[] = "Failover_Partner"; const char KeyStoreAuthentication[] = "KeyStoreAuthentication"; const char KeyStorePrincipalId[] = "KeyStorePrincipalId"; const char KeyStoreSecret[] = "KeyStoreSecret"; const char LoginTimeout[] = "LoginTimeout"; const char MARS_ODBC[] = "MARS_Connection"; const char MultiSubnetFailover[] = "MultiSubnetFailover"; const char QuotedId[] = "QuotedId"; const char TraceFile[] = "TraceFile"; const char TraceOn[] = "TraceOn"; const char TrustServerCertificate[] = "TrustServerCertificate"; const char TransactionIsolation[] = "TransactionIsolation"; const char TransparentNetworkIPResolution[] = "TransparentNetworkIPResolution"; const char WSID[] = "WSID"; const char UID[] = "UID"; const char PWD[] = "PWD"; const char SERVER[] = "Server"; } enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_INVALID, SQLSRV_CONN_OPTION_APP, SQLSRV_CONN_OPTION_ACCESS_TOKEN, SQLSRV_CONN_OPTION_CHARACTERSET, SQLSRV_CONN_OPTION_CONN_POOLING, SQLSRV_CONN_OPTION_DATABASE, SQLSRV_CONN_OPTION_ENCRYPT, SQLSRV_CONN_OPTION_FAILOVER_PARTNER, SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, SQLSRV_CONN_OPTION_MARS, SQLSRV_CONN_OPTION_QUOTED_ID, SQLSRV_CONN_OPTION_TRACE_FILE, SQLSRV_CONN_OPTION_TRACE_ON, SQLSRV_CONN_OPTION_TRANS_ISOLATION, SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, SQLSRV_CONN_OPTION_WSID, SQLSRV_CONN_OPTION_ATTACHDBFILENAME, SQLSRV_CONN_OPTION_APPLICATION_INTENT, SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, SQLSRV_CONN_OPTION_AUTHENTICATION, SQLSRV_CONN_OPTION_COLUMNENCRYPTION, SQLSRV_CONN_OPTION_DRIVER, SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION, SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID, SQLSRV_CONN_OPTION_KEYSTORE_SECRET, SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION, SQLSRV_CONN_OPTION_CONN_RETRY_COUNT, SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL, // Driver specific connection options SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, }; #define NO_ATTRIBUTE -1 // type of connection attributes enum CONN_ATTR_TYPE { CONN_ATTR_INT, CONN_ATTR_BOOL, CONN_ATTR_STRING, CONN_ATTR_INVALID, }; // 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; unsigned int conn_option_key; // the name of the option in the ODBC connection string const char * odbc_name; unsigned int odbc_len; enum CONN_ATTR_TYPE value_type; // process the connection type // return whether or not the function was successful in processing the connection option void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); }; // connection attribute functions // simply add the parsed value to the connection string struct conn_str_append_func { static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str TSRMLS_DC ); }; struct conn_null_func { static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC ); }; struct column_encryption_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; struct driver_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; struct ce_akv_str_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; struct access_token_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; // factory to create a connection (since they are subclassed to instantiate statements) typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); // *** connection functions *** sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, _Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[], _In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC ); SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled ); void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC ); void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval *server_version TSRMLS_DC ); void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len ); size_t core_str_zval_is_true( _Inout_ zval* str_zval ); bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t value_len ); bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version ); bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN r, _In_ const char* error_state ); //********************************************************************************************************************************* // Statement //********************************************************************************************************************************* struct stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ TSRMLS_DC ); }; struct stmt_option_query_timeout : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; struct stmt_option_send_at_exec : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; struct stmt_option_buffered_query_limit : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; struct stmt_option_date_as_string : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; struct stmt_option_format_decimals : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; struct stmt_option_decimal_places : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; // used to hold the table for statment options struct stmt_option { const char * name; // name of the statement option unsigned int name_len; // name length unsigned int key; std::unique_ptr func; // callback that actually handles the work of the option }; // holds the stream param and the encoding that it was assigned struct sqlsrv_stream { zval* stream_z; SQLSRV_ENCODING encoding; SQLUSMALLINT field_index; SQLSMALLINT sql_type; sqlsrv_stmt* stmt; sqlsrv_stream( _In_opt_ zval* str_z, _In_ SQLSRV_ENCODING enc ) : stream_z( str_z ), encoding( enc ), field_index( 0 ), sql_type( SQL_UNKNOWN_TYPE ), stmt( NULL ) { } sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), field_index( 0 ), sql_type( SQL_UNKNOWN_TYPE ), stmt( NULL ) { } }; // close any active stream void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); extern php_stream_wrapper g_sqlsrv_stream_wrapper; // resource constants used when registering the stream type with PHP #define SQLSRV_STREAM_WRAPPER "sqlsrv" #define SQLSRV_STREAM "sqlsrv_stream" // *** parameter metadata struct *** struct param_meta_data { SQLSMALLINT sql_type; SQLSMALLINT decimal_digits; SQLSMALLINT nullable; SQLULEN column_size; param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0) { } ~param_meta_data() { } SQLSMALLINT get_sql_type() { return sql_type; } SQLSMALLINT get_decimal_digits() { return decimal_digits; } SQLSMALLINT get_nullable() { return nullable; } SQLULEN get_column_size() { return column_size; } }; // holds the output parameter information. Strings also need the encoding and other information for // after processing. Only integer, float, and strings are allowable output parameters. struct sqlsrv_output_param { zval* param_z; SQLSRV_ENCODING encoding; SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary bool is_bool; param_meta_data meta_data; // parameter meta data // string output param constructor sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) : param_z(p_z), encoding(enc), param_num(num), original_buffer_len(buffer_len), is_bool(false), php_out_type(SQLSRV_PHPTYPE_INVALID) { } // every other type output parameter constructor sqlsrv_output_param( _In_ zval* p_z, _In_ int num, _In_ bool is_bool, _In_ SQLSRV_PHPTYPE php_out_type) : param_z( p_z ), encoding( SQLSRV_ENCODING_INVALID ), param_num( num ), original_buffer_len( -1 ), is_bool( is_bool ), php_out_type(php_out_type) { } void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE) { meta_data.sql_type = sql_type; meta_data.column_size = column_size; meta_data.decimal_digits = decimal_digits; meta_data.nullable = nullable; } param_meta_data& getMetaData() { return meta_data; } }; // forward decls struct sqlsrv_result_set; struct field_meta_data; // *** Statement resource structure *** struct sqlsrv_stmt : public sqlsrv_context { void free_param_data( TSRMLS_D ); virtual void new_result_set( TSRMLS_D ); sqlsrv_conn* conn; // Connection that created this statement bool executed; // Whether the statement has been executed yet (used for error messages) bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row sqlsrv_result_set* current_results; // Current result set SQLULEN cursor_type; // Type of cursor for the current result set bool has_rows; // Has_rows is set if there are actual rows in the row set bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the // last results unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings bool format_decimals; // false by default but the user can set this to true to add the missing leading zeroes and/or control number of decimal digits to show short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits) // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP zval output_params; // hold all the output parameters zval param_streams; // track which streams to send data to the server zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects bool send_streams_at_exec; // send all stream data right after execution before returning sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string // to the server) zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals zval col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch. zval active_stream; // the currently active stream reading data from the database std::vector param_descriptions; // meta data for current result set std::vector> current_meta_data; sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC ); virtual ~sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ) = 0; }; // *** field metadata struct *** struct field_meta_data { sqlsrv_malloc_auto_ptr field_name; SQLSMALLINT field_name_len; SQLSMALLINT field_type; SQLULEN field_size; SQLULEN field_precision; SQLSMALLINT field_scale; SQLSMALLINT field_is_nullable; bool field_is_money_type; field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), field_scale (0), field_is_nullable(0), field_is_money_type(false) { } ~field_meta_data() { } }; // *** statement constants *** // unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) // uninitialized query timeout value const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; // special buffered query constant #ifndef _WIN32 const size_t SQLSRV_CURSOR_BUFFERED = 42; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant #else const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant #endif // !_WIN32 // factory to create a statement typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); // *** statement functions *** sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC ); void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z, _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, _Inout_ SQLSMALLINT decimal_digits TSRMLS_DC ); SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 ); field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno TSRMLS_DC ); bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset TSRMLS_DC ); void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_phptype, _In_ bool prefer_string, _Outref_result_bytebuffer_maybenull_(*field_length) void*& field_value, _Inout_ SQLLEN* field_length, _In_ bool cache_field, _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true ); void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong paramno, zval* param_z TSRMLS_DC ); void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type TSRMLS_DC ); void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout TSRMLS_DC ); void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC ); void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ); bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC ); void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC); //********************************************************************************************************************************* // Result Set //********************************************************************************************************************************* // Abstract the result set so that a result set can either be used as is from ODBC or buffered. // This is not a complete abstraction of a result set. Only enough is abstracted to allow for // information and capabilities normally not available when a result set is not buffered // (e.g., forward only vs buffered means row count is available and cursor movement is possible). // Otherwise, normal ODBC calls are still valid and should be used to get information about the // result set (e.g., SQLNumResultCols). struct sqlsrv_result_set { sqlsrv_stmt* odbc; explicit sqlsrv_result_set( _In_ sqlsrv_stmt* ); virtual ~sqlsrv_result_set( void ) { } virtual bool cached( int field_index ) = 0; virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset TSRMLS_DC ) = 0; virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC )= 0; virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ) = 0; virtual SQLLEN row_count( TSRMLS_D ) = 0; }; struct sqlsrv_odbc_result_set : public sqlsrv_result_set { explicit sqlsrv_odbc_result_set( _In_ sqlsrv_stmt* ); virtual ~sqlsrv_odbc_result_set( void ); virtual bool cached( int field_index ) { return false; } virtual SQLRETURN fetch( _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset TSRMLS_DC ); virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, _In_ bool handle_warning TSRMLS_DC ); virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); private: // prevent invalid instantiations and assignments sqlsrv_odbc_result_set( void ); sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); }; struct sqlsrv_buffered_result_set : public sqlsrv_result_set { struct meta_data { SQLSMALLINT type; SQLSMALLINT c_type; // convenience SQLULEN offset; // in bytes SQLULEN length; // in bytes SQLSMALLINT scale; static const SQLULEN SIZE_UNKNOWN = 0; }; // default maximum amount of memory that a buffered query can consume #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; explicit sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* odbc TSRMLS_DC ); virtual ~sqlsrv_buffered_result_set( void ); virtual bool cached( int field_index ) { return true; } virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset TSRMLS_DC ); virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ); virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); // buffered result set specific SQLSMALLINT column_count( void ) { return col_count; } struct meta_data& col_meta_data( SQLSMALLINT i ) { return meta[i]; } private: // prevent invalid instantiations and assignments sqlsrv_buffered_result_set( void ); sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); HashTable* cache; // rows of data kept in index based hash table SQLSMALLINT col_count; // number of columns in the current result set sqlsrv_malloc_auto_ptr meta; // metadata for fields in the cache SQLLEN current; // 1 based, 0 means before first row sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here SQLUSMALLINT last_field_index; // the last field data retrieved from SQLLEN read_so_far; // position within string to read from (for partial reads of strings) sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use SQLLEN temp_length; // number of bytes in the temp conversion buffer typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; // two dimentional sparse matrix that holds the [from][to] functions that do conversions static conv_matrix_t conv_matrix; // string conversion functions SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN binary_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); SQLRETURN to_binary_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); SQLRETURN to_same_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); SQLRETURN wide_to_system_string( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_to_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); // long conversion functions SQLRETURN to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); SQLRETURN long_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN long_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); // double conversion functions SQLRETURN to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); SQLRETURN double_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN double_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN double_to_long( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); // string to number conversion functions // Future: See if these can be converted directly to template member functions SQLRETURN string_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); // utility functions for conversions unsigned char* get_row( void ); }; //********************************************************************************************************************************* // Utility //********************************************************************************************************************************* // Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. // We use this since the unused variables are buried in the PHP_FUNCTION macro. #define SQLSRV_UNUSED( var ) var; // do a heap check in debug mode, but only print errors, not all of the allocations #define MEMCHECK_SILENT 1 // utility functions shared by multiple callers across files bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len); bool validate_string( _In_ char* string, _In_ SQLLEN& len); bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ SQLLEN& cchOutLen ); SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); //********************************************************************************************************************************* // Error handling routines and Predefined Errors //********************************************************************************************************************************* enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_ODBC, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, SQLSRV_ERROR_CE_DRIVER_REQUIRED, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND, SQLSRV_ERROR_ZEND_HASH, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, SQLSRV_ERROR_ZEND_STREAM, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, SQLSRV_ERROR_FETCH_PAST_END, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, SQLSRV_ERROR_NO_FIELDS, SQLSRV_ERROR_INVALID_TYPE, SQLSRV_ERROR_FETCH_NOT_CALLED, SQLSRV_ERROR_NO_DATA, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, SQLSRV_ERROR_NEXT_RESULT_PAST_END, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, SQLSRV_ERROR_INVALID_CONNECTION_KEY, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, SQLSRV_ERROR_INVALID_OPTION_KEY, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, SQLSRV_ERROR_STREAM_CREATE, SQLSRV_ERROR_MARS_OFF, SQLSRV_ERROR_FIELD_INDEX_ERROR, SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED, SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION, SQLSRV_ERROR_AKV_AUTH_MISSING, SQLSRV_ERROR_AKV_NAME_MISSING, SQLSRV_ERROR_AKV_SECRET_MISSING, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, SQLSRV_ERROR_INVALID_DECIMAL_PLACES, SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, }; // SQLSTATE for all internal errors extern SQLCHAR IMSSP[]; // SQLSTATE for all internal warnings extern SQLCHAR SSPWARN[]; // flags passed to sqlsrv_errors to filter its return values enum error_handling_flags { SQLSRV_ERR_ERRORS, SQLSRV_ERR_WARNINGS, SQLSRV_ERR_ALL }; // *** internal error macros and functions *** // call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the // errno is 1 based. It returns it as an array with 3 members: // 1/SQLSTATE) sqlstate // 2/code) driver specific error code // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity TSRMLS_DC ); // format and return a driver specfic error void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, _Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity TSRMLS_DC, _In_opt_ va_list* args ); // return the message for the HRESULT returned by GetLastError. Some driver errors use this to // return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. const char* get_last_error_message( _Inout_ DWORD last_error = 0 ); // a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer DWORD core_sqlsrv_format_message( _Out_ char* output_buffer, _In_ unsigned output_len, _In_opt_ const char* format, ... ); // convenience functions that overload either a reference or a pointer so we can use // either in the CHECK_* functions. inline bool call_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned long sqlsrv_error_code TSRMLS_DC, _In_ bool warning, ... ) { va_list print_params; va_start( print_params, warning ); bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); va_end( print_params ); return ignored; } inline bool call_error_handler( _Inout_ sqlsrv_context* ctx, _In_ unsigned long sqlsrv_error_code TSRMLS_DC, _In_ bool warning, ... ) { va_list print_params; va_start( print_params, warning ); bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); va_end( print_params ); return ignored; } // PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which // we don't want on a web server #define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, ## __VA_ARGS__ ); #if defined( PHP_DEBUG ) #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ if( !(condition)) { \ DIE (msg, ## __VA_ARGS__ ); \ } #else #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) #endif // check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. inline bool is_truncated_warning( _In_ SQLCHAR* state ) { #if defined(ZEND_DEBUG) if( state == NULL || strnlen_s( reinterpret_cast( state )) != 5 ) { \ DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ } #endif return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); } // Macros for handling errors. These macros are simplified if statements that take boilerplate // code down to a single line to avoid distractions in the code. #define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ bool flag##unique = (condition); \ bool ignored##unique = true; \ if (flag##unique) { \ ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, ## __VA_ARGS__ ); \ } \ if( !ignored##unique ) #define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ CHECK_ERROR_EX( unique, condition, context, ssphp, ## __VA_ARGS__ ) #define CHECK_ERROR( condition, context, ... ) \ CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, 0, ## __VA_ARGS__ ) #define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) #define CHECK_SQL_ERROR( result, context, ... ) \ SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ CHECK_ERROR( result == SQL_ERROR, context, ## __VA_ARGS__ ) #define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ bool ignored##unique = true; \ if( condition ) { \ ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, ## __VA_ARGS__ ); \ } \ if( !ignored##unique ) #define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, ## __VA_ARGS__ ) #define CHECK_SQL_WARNING( result, context, ... ) \ if( result == SQL_SUCCESS_WITH_INFO ) { \ (void)call_error_handler( context, 0 TSRMLS_CC, /*warning*/ true, ## __VA_ARGS__ ); \ } #define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) #define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, ## __VA_ARGS__ ) \ #define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ bool ignored = true; \ if( result == SQL_ERROR ) { \ ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, ##__VA_ARGS__ ); \ } \ else if( result == SQL_SUCCESS_WITH_INFO ) { \ ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, ##__VA_ARGS__ ); \ } \ if( !ignored ) // throw an exception after it has been hooked into the custom error handler #define THROW_CORE_ERROR( ctx, custom, ... ) \ (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, ## __VA_ARGS__ ); \ throw core::CoreException(); //********************************************************************************************************************************* // ODBC/Zend function wrappers //********************************************************************************************************************************* namespace core { // base exception for the driver struct CoreException : public std::exception { CoreException() { } }; inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC ) { // Skip this if not SQL_ERROR - // 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 (r == SQL_ERROR) { SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); if (rtemp == SQL_SUCCESS_WITH_INFO && len > SQL_MAX_MESSAGE_LENGTH) { // if the error message is this long, then it must not be the mars message // defined as ODBC_CONNECTION_BUSY_ERROR -- so return here and continue the // regular error handling return; } CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) { throw CoreException(); } // the message returned by ODBC Driver for SQL Server const std::string connection_busy_error( ODBC_CONNECTION_BUSY_ERROR ); const std::string returned_error( reinterpret_cast( err_msg )); if(( returned_error.find( connection_busy_error ) != std::string::npos )) { THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); } } } // *** ODBC wrappers *** // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors // some of the signatures have been altered to be more convenient since the return value is no longer // required to return the status of the call (e.g., SQLNumResultCols). // These functions take the sqlsrv_context type. However, since the error handling code can alter // the context to hold the error, they are not passed as const. inline SQLRETURN SQLGetDiagField( _Inout_ sqlsrv_context* ctx, _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Out_writes_opt_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Out_opt_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, diag_info_buffer, buffer_length, out_buffer_length ); CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { throw CoreException(); } return r; } inline void SQLAllocHandle( _In_ SQLSMALLINT HandleType, _Inout_ sqlsrv_context& InputHandle, _Out_ SQLHANDLE* OutputHandlePtr TSRMLS_DC ) { SQLRETURN r; r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { throw CoreException(); } } inline void SQLBindParameter( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT ParameterNumber, _In_ SQLSMALLINT InputOutputType, _In_ SQLSMALLINT ValueType, _In_ SQLSMALLINT ParameterType, _In_ SQLULEN ColumnSize, _In_ SQLSMALLINT DecimalDigits, _Inout_opt_ SQLPOINTER ParameterValuePtr, _Inout_ SQLLEN BufferLength, _Inout_ SQLLEN * StrLen_Or_IndPtr TSRMLS_DC ) { SQLRETURN r; r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { SQLRETURN r = ::SQLCloseCursor( stmt->handle() ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num TSRMLS_DC ) { SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, buffer_length, out_buffer_length, field_type_num ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLColAttributeW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num TSRMLS_DC ) { SQLRETURN r = ::SQLColAttributeW( stmt->handle(), field_index, field_identifier, field_type_char, buffer_length, out_buffer_length, field_type_num ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLDescribeCol( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Out_writes_opt_(col_name_length) SQLCHAR* col_name, _In_ SQLSMALLINT col_name_length, _Out_opt_ SQLSMALLINT* col_name_length_out, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size, _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) { SQLRETURN r; r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, data_type, col_size, decimal_digits, nullable); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLDescribeColW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Out_writes_opt_(col_name_length) SQLWCHAR* col_name, _In_ SQLSMALLINT col_name_length, _Out_opt_ SQLSMALLINT* col_name_length_out, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size, _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) { SQLRETURN r; r = ::SQLDescribeColW( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, data_type, col_size, decimal_digits, nullable ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLDescribeParam( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT paramno, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size, _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) { SQLRETURN r; r = ::SQLDescribeParam( stmt->handle(), paramno, data_type, col_size, decimal_digits, nullable ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLNumParams( _Inout_ sqlsrv_stmt* stmt, _Out_opt_ SQLSMALLINT* num_params) { SQLRETURN r; r = ::SQLNumParams( stmt->handle(), num_params ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLEndTran( _In_ SQLSMALLINT handleType, _Inout_ sqlsrv_conn* conn, _In_ SQLSMALLINT completionType TSRMLS_DC ) { SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); CHECK_SQL_ERROR_OR_WARNING( r, conn ) { throw CoreException(); } } // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success inline SQLRETURN SQLExecDirect( _Inout_ sqlsrv_stmt* stmt, _In_ char* sql TSRMLS_DC ) { SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); check_for_mars_error( stmt, r TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return r; } inline SQLRETURN SQLExecDirectW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLWCHAR* wsql TSRMLS_DC ) { SQLRETURN r; r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); check_for_mars_error( stmt, r TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return r; } // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success inline SQLRETURN SQLExecute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { SQLRETURN r; r = ::SQLExecute( stmt->handle() ); check_for_mars_error( stmt, r TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return r; } inline SQLRETURN SQLFetchScroll( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset TSRMLS_DC ) { SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return r; } // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine inline void SQLFreeHandle( _Inout_ sqlsrv_context& ctx TSRMLS_DC ) { SQLRETURN r; r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} } inline void SQLGetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _Out_writes_opt_(buf_len) void* value_ptr, _In_ SQLINTEGER buf_len, _Out_opt_ SQLINTEGER* str_len TSRMLS_DC) { SQLRETURN r; r = ::SQLGetStmtAttr( stmt->handle(), attr, value_ptr, buf_len, str_len ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline SQLRETURN SQLGetData( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_opt_ SQLLEN* out_buffer_length, _In_ bool handle_warning TSRMLS_DC ) { SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); if( r == SQL_NO_DATA ) return r; CHECK_SQL_ERROR( r, stmt ) { throw CoreException(); } if( handle_warning ) { CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { throw CoreException(); } } return r; } inline void SQLGetInfo( _Inout_ sqlsrv_conn* conn, _In_ SQLUSMALLINT info_type, _Out_writes_bytes_opt_(buffer_len) SQLPOINTER info_value, _In_ SQLSMALLINT buffer_len, _Out_opt_ SQLSMALLINT* str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); CHECK_SQL_ERROR_OR_WARNING( r, conn ) { throw CoreException(); } } inline void SQLGetTypeInfo( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT data_type TSRMLS_DC ) { SQLRETURN r; r = ::SQLGetTypeInfo( stmt->handle(), data_type ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. inline SQLRETURN SQLMoreResults( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { SQLRETURN r = ::SQLMoreResults( stmt->handle() ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return r; } inline SQLSMALLINT SQLNumResultCols( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { SQLRETURN r; SQLSMALLINT num_cols; r = ::SQLNumResultCols( stmt->handle(), &num_cols ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return num_cols; } // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more // parameters or when the parameters are all processed. inline SQLRETURN SQLParamData( _Inout_ sqlsrv_stmt* stmt, _Out_opt_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) { SQLRETURN r; r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return r; } inline void SQLPrepareW( _Inout_ sqlsrv_stmt* stmt, _In_reads_(sql_len) SQLWCHAR * sql, _In_ SQLINTEGER sql_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline void SQLPutData( _Inout_ sqlsrv_stmt* stmt, _In_reads_(strlen_or_ind) SQLPOINTER data_ptr, _In_ SQLLEN strlen_or_ind TSRMLS_DC ) { SQLRETURN r; r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } inline SQLLEN SQLRowCount( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { SQLRETURN r; SQLLEN rows_affected; r = ::SQLRowCount( stmt->handle(), &rows_affected ); // On Linux platform // DriverName: libmsodbcsql-13.0.so.0.0 // DriverODBCVer: 03.52 // DriverVer: 13.00.0000 // unixODBC: 2.3.1 // r = ::SQLRowCount( stmt->handle(), &rows_affected ); // returns r=-1 for an empty result set. #ifndef _WIN32 if( r == -1 && rows_affected == -1 ) return 0; #endif // !_WIN32 CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } return rows_affected; } inline void SQLSetConnectAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { throw CoreException(); } } inline void SQLSetDescField( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT rec_num, _In_ SQLSMALLINT fld_id, _In_reads_bytes_opt_( str_len ) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) { SQLRETURN r; SQLHDESC hIpd = NULL; core::SQLGetStmtAttr( stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0 ); if( value_ptr ) { r = ::SQLSetDescField( hIpd, rec_num, fld_id, value_ptr, str_len ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } } inline void SQLSetEnvAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { throw CoreException(); } } inline void SQLSetConnectAttr( _Inout_ sqlsrv_conn* conn, _In_ SQLINTEGER attribute, _In_reads_bytes_opt_(value_len) SQLPOINTER value_ptr, _In_ SQLINTEGER value_len TSRMLS_DC ) { SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); CHECK_SQL_ERROR_OR_WARNING( r, conn ) { throw CoreException(); } } inline void SQLSetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _In_reads_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } } // *** zend wrappers *** //zend_resource_dtor sets the type of destroyed resources to -1 #define RSRC_INVALID_TYPE -1 // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string // so allocated memory inside of value_z should be released before assigning it to the new string inline void sqlsrv_zval_stringl( _Inout_ zval* value_z, _In_reads_(str_len) const char* str, _In_ const std::size_t str_len) { if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { zend_string* temp_zstr = zend_string_init(str, str_len, 0); zend_string_release(Z_STR_P(value_z)); ZVAL_NEW_STR(value_z, temp_zstr); } else { ZVAL_STRINGL(value_z, str, str_len); } } // exception thrown when a zend function wrapped here fails. // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error // that can be thrown from it. inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value TSRMLS_DC) { int zr = ::add_index_zval( array, index, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value TSRMLS_DC) { int zr = ::add_next_index_zval( array, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_add_assoc_null( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key TSRMLS_DC ) { int zr = ::add_assoc_null( array_z, key ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_add_assoc_long( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zend_long val TSRMLS_DC ) { int zr = ::add_assoc_long( array_z, key, val ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_add_assoc_string( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _Inout_z_ char* val, _In_ bool duplicate TSRMLS_DC ) { int zr = ::add_assoc_string(array_z, key, val); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } if (duplicate == 0) { sqlsrv_free(val); } } inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { #if PHP_VERSION_ID < 70300 CHECK_ZEND_ERROR(::array_init(new_array), ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } #else array_init(new_array); #endif } inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z TSRMLS_DC ) { // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment php_stream_from_zval_no_verify( stream, stream_z ); CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { throw CoreException(); } } inline void sqlsrv_zend_hash_get_current_data( _In_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ zval*& output_data TSRMLS_DC) { int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_zend_hash_get_current_data_ptr( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ void*& output_data TSRMLS_DC) { int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } inline void sqlsrv_zend_hash_index_del( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index TSRMLS_DC ) { int zr = ::zend_hash_index_del( ht, index ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_zend_hash_index_update( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ zval* data_z TSRMLS_DC ) { int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_zend_hash_index_update_ptr( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ void* pData TSRMLS_DC) { int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } inline void sqlsrv_zend_hash_index_update_mem( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_reads_bytes_(size) void* pData, _In_ std::size_t size TSRMLS_DC) { int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } inline void sqlsrv_zend_hash_next_index_insert( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zval* data TSRMLS_DC ) { int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } inline void sqlsrv_zend_hash_next_index_insert_mem( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _In_reads_bytes_(data_size) void* data, _In_ uint data_size TSRMLS_DC) { int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } inline void sqlsrv_zend_hash_next_index_insert_ptr( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ void* data TSRMLS_DC) { int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, _Inout_ HashTable* ht, _Inout_ uint32_t initial_size, _In_ dtor_func_t dtor_fn, _In_ zend_bool persistent TSRMLS_DC ) { ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); } template sqlsrv_stmt* allocate_stmt( _In_ sqlsrv_conn* conn, _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) { return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); } template sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) { return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); } } // namespace core template struct str_conn_attr_func { static void func( connection_option const* /*option*/, zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) { try { core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), static_cast( Z_STRLEN_P( value )) TSRMLS_CC ); } catch ( core::CoreException& ) { throw; } } }; #endif // CORE_SQLSRV_H