php-sqlsrv/source/shared/core_sqlsrv.h

2351 lines
88 KiB
C
Raw Normal View History

#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
//
2016-08-24 23:28:20 +02:00
// Microsoft Drivers 4.1 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
//*********************************************************************************************************************************
2017-01-24 02:22:15 +01:00
#ifdef SQL_WCHART_CONVERT
#undef SQL_WCHART_CONVERT
#endif
#ifndef _WCHART_DEFINED
#define _WCHART_DEFINED
#endif
2017-01-26 22:54:02 +01:00
#include "php.h"
#include "php_globals.h"
#include "php_ini.h"
#include "ext/standard/php_standard.h"
#include "ext/standard/info.h"
2017-01-24 02:22:15 +01:00
#ifndef _WIN32
#include "FormattedPrint.h"
#include "StringFunctions.h"
2017-01-26 22:54:02 +01:00
#endif // !_WIN32
2017-01-24 02:22:15 +01:00
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef PHP_WIN32
#define PHP_SQLSRV_API __declspec(dllexport)
#else
#define PHP_SQLSRV_API
#endif
2017-01-24 02:22:15 +01:00
// #define MultiByteToWideChar SystemLocale::ToUtf16
#define stricmp strcasecmp
#define strnicmp strncasecmp
#ifdef __linux__
#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
// OACR is an internal Microsoft static code analysis tool
#if defined(OACR)
#include <oacr.h>
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" {
2017-01-24 02:22:15 +01:00
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 )
2017-01-24 02:22:15 +01:00
#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
2017-01-24 02:22:15 +01:00
#if defined(_MSC_VER)
#pragma warning(pop)
2017-01-24 02:22:15 +01:00
#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
2017-01-26 22:54:02 +01:00
#ifdef _WIN32
#include <sql.h>
#include <sqlext.h>
2017-01-26 22:54:02 +01:00
#endif // _WIN32
#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 <deque>
#include <map>
#include <algorithm>
#include <limits>
#include <cassert>
#include <memory>
// included for SQL Server specific constants
#include "msodbcsql.h"
2017-01-25 23:05:08 +01:00
#ifdef _WIN32
#include <strsafe.h>
#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;
// 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;
// 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;
// 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 <bool b>
struct sqlsrv_static_assert;
template <>
struct sqlsrv_static_assert<true> { 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)( unsigned int severity TSRMLS_DC, const char* msg, 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( log_callback );
// a simple wrapper around a PHP error logging function.
void write_to_log( unsigned int severity TSRMLS_DC, 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( 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( size_t size, const char* file, 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( size_t element_count, size_t element_size, size_t extra, const char* file, 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, size_t size, const char* file, 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( void* ptr, const char* file, 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( size_t size )
{
return emalloc( size );
}
inline void* sqlsrv_malloc( size_t element_count, size_t element_size, 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( void* buffer, size_t size )
{
return erealloc( buffer, size );
}
inline void sqlsrv_free( void* ptr )
{
efree( ptr );
}
#endif
// trait class that allows us to assign const types to an auto_ptr
template <typename T>
struct remove_const {
typedef T type;
};
template <typename T>
struct remove_const<const T*> {
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<typename T>
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<typename U>
struct rebind {
typedef sqlsrv_allocator<U> other;
};
inline sqlsrv_allocator() {}
inline ~sqlsrv_allocator() {}
inline sqlsrv_allocator( sqlsrv_allocator const& ) {}
template<typename U>
inline sqlsrv_allocator( sqlsrv_allocator<U> const& ) {}
// address (doesn't work if the class defines operator&)
inline pointer address( reference r )
{
return &r;
}
inline const_pointer address( const_reference r )
{
return &r;
}
// memory allocation/deallocation
inline pointer allocate( size_type cnt,
typename std::allocator<void>::const_pointer = 0 )
{
return reinterpret_cast<pointer>( sqlsrv_malloc(cnt, sizeof (T), 0));
}
inline void deallocate( pointer p, size_type )
{
sqlsrv_free(p);
}
// size
inline size_type max_size( void ) const
{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
// object construction/destruction
inline void construct( pointer p, const T& t )
{
new(p) T(t);
}
inline void destroy(pointer p)
{
p->~T();
}
// equality operators
inline bool operator==( sqlsrv_allocator const& )
{
return true;
}
inline bool operator!=( 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 <typename T, typename Subclass>
class sqlsrv_auto_ptr {
public:
sqlsrv_auto_ptr( void ) : _ptr( NULL )
{
}
~sqlsrv_auto_ptr( void )
{
static_cast<Subclass*>(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<T*>::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[]( 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[]( 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[]( 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[](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[]( 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( T* ptr ) :
_ptr( ptr )
{
}
sqlsrv_auto_ptr( sqlsrv_auto_ptr& 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 )
{
static_cast<Subclass*>( 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 <typename T>
class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> > {
public:
sqlsrv_malloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( NULL )
{
}
sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( T* ptr = NULL )
{
if( sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr )
sqlsrv_free( (void*) sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr );
sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr = ptr;
}
T* operator=( T* ptr )
{
return sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >::operator=( ptr );
}
void operator=( sqlsrv_malloc_auto_ptr<T>& 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( size_t new_size )
{
sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr = reinterpret_cast<T*>( sqlsrv_realloc( sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_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<HashTable, hash_auto_ptr> {
public:
hash_auto_ptr( void ) :
sqlsrv_auto_ptr<HashTable, hash_auto_ptr>( NULL )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( HashTable* ptr = NULL )
{
if( _ptr ) {
zend_hash_destroy( _ptr );
FREE_HASHTABLE( _ptr );
}
_ptr = ptr;
}
HashTable* operator=( HashTable* ptr )
{
return sqlsrv_auto_ptr<HashTable, hash_auto_ptr>::operator=( ptr );
}
private:
hash_auto_ptr( HashTable const& hash );
hash_auto_ptr( 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<zval, zval_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( zval* ptr = NULL )
{
if( _ptr )
zval_ptr_dtor(_ptr );
_ptr = ptr;
}
zval* operator=( zval* ptr )
{
return sqlsrv_auto_ptr<zval, zval_auto_ptr>::operator=( ptr );
}
private:
zval_auto_ptr( 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( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false )
{
sqlstate = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 ));
strcpy_s( reinterpret_cast<char*>( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast<const char*>( sql_state ));
strcpy_s( reinterpret_cast<char*>( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>( message ));
native_code = code;
format = printf_format;
}
sqlsrv_error( 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<sqlsrv_error, sqlsrv_error_auto_ptr > {
public:
sqlsrv_error_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >( NULL )
{
}
sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_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( sqlsrv_error* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_error();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_error* operator=( sqlsrv_error* ptr )
{
return sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_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=( 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)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, 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( SQLSMALLINT type, error_callback e, void* drv, 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( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) :
handle_( h ),
handle_type_( t ),
name_( NULL ),
err_( e ),
driver_( drv ),
last_error_(),
encoding_( encoding )
{
}
sqlsrv_context( 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( const char* f )
{
name_ = f;
}
void set_last_error( 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( 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( 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( 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 )
{
}
};
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// variables set during initialization
extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in
extern HashTable* g_encodings; // encodings supported by this driver
void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC );
void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp );
// environment context used by sqlsrv_connect for when a connection error occurs.
struct sqlsrv_henv {
sqlsrv_context ctx;
sqlsrv_henv( SQLHANDLE handle, error_callback e, 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
};
2016-06-13 23:44:53 +02:00
// supported driver versions.
enum DRIVER_VERSION : size_t {
MIN = 0,
ODBC_DRIVER_13 = MIN,
ODBC_DRIVER_11 = 1,
MAX = ODBC_DRIVER_11,
};
// forward decl
struct sqlsrv_stmt;
struct stmt_option;
// *** 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
2016-06-13 23:44:53 +02:00
DRIVER_VERSION driver_version;
// initialize with default values
sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
server_version = SERVER_VERSION_UNKNOWN;
driver_version = ODBC_DRIVER_13;
}
// 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,
// Driver specific connection options
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
};
namespace ODBCConnOptions {
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
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 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_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,
// 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 );
};
// simply add the parsed value to the connection string
struct conn_str_append_func {
static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, 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 );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC );
// *** connection functions ***
sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
const char* server, const char* uid, const char* pwd,
HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[],
void* driver, const char* driver_func TSRMLS_DC );
void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC );
void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC );
void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC );
void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC );
bool core_is_conn_opt_value_escaped( const char* value, size_t value_len );
size_t core_str_zval_is_true( zval* str_zval );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_functor {
virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC );
};
struct stmt_option_query_timeout : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
struct stmt_option_send_at_exec : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
struct stmt_option_buffered_query_limit : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, 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<stmt_option_functor> 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( zval* str_z, 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"
// 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
bool is_bool;
// string output param constructor
sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) :
param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false )
{
}
// every other type output parameter constructor
sqlsrv_output_param( zval* p_z, int num, bool is_bool ) :
param_z( p_z ),
encoding( SQLSRV_ENCODING_INVALID ),
param_num( num ),
original_buffer_len( -1 ),
is_bool( is_bool )
{
}
};
// forward decls
struct sqlsrv_result_set;
// *** 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)
// 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<SQLLEN> 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
2016-06-13 23:44:53 +02:00
zval active_stream; // the currently active stream reading data from the database
sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, 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
2017-01-17 21:38:31 +01:00
virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0;
};
// *** field metadata struct ***
struct field_meta_data {
sqlsrv_malloc_auto_ptr<SQLCHAR> field_name;
SQLSMALLINT field_name_len;
SQLSMALLINT field_type;
SQLULEN field_size;
SQLULEN field_precision;
SQLSMALLINT field_scale;
SQLSMALLINT field_is_nullable;
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
field_scale (0), field_is_nullable(0)
{
}
~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
#ifdef __linux__
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
// 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( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht,
const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC );
void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z,
SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size,
SQLSMALLINT decimal_digits TSRMLS_DC );
void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 );
field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC );
bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC );
void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string,
_Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field,
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC);
bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true );
void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC );
2016-05-04 05:05:41 +02:00
void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC );
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC );
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit 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( sqlsrv_stmt* );
virtual ~sqlsrv_result_set( void ) { }
virtual bool cached( int field_index ) = 0;
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0;
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
_Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )= 0;
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
_Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
_Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0;
virtual sqlsrv_error* get_diag_rec( 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( sqlsrv_stmt* );
virtual ~sqlsrv_odbc_result_set( void );
virtual bool cached( int field_index ) { return false; }
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
_Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC );
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
_Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
_Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC );
virtual sqlsrv_error* get_diag_rec( 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( sqlsrv_stmt* odbc TSRMLS_DC );
virtual ~sqlsrv_buffered_result_set( void );
virtual bool cached( int field_index ) { return true; }
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
_Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC );
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
_Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
_Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC );
virtual sqlsrv_error* get_diag_rec( 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
meta_data* 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<SQLCHAR> 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)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
// long conversion functions
SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length );
SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
// double conversion functions
SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length );
SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length,
_Out_ 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( SQLSRV_ENCODING encoding, char** string, SQLLEN& len);
bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len);
bool validate_string(char* string, SQLLEN& len);
bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const SQLWCHAR* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen );
SQLWCHAR* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, 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_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,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
};
// the message returned by ODBC Driver 11 for SQL Server
2016-06-13 23:44:53 +02:00
static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command",
"[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" };
// 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( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error,
logging_severity severity TSRMLS_DC );
// format and return a driver specfic error
void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error,
sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, 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( DWORD last_error = 0 );
// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer
2016-10-25 01:11:52 +02:00
DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, 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( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, 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( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, 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
2017-01-24 02:22:15 +01:00
#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, ## __VA_ARGS__ );
#if defined( PHP_DEBUG )
#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \
if( !(condition)) { \
2017-01-24 02:22:15 +01:00
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( SQLCHAR* state )
{
#if defined(ZEND_DEBUG)
if( state == NULL || strlen( reinterpret_cast<char*>( 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) { \
2017-01-24 02:22:15 +01:00
ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, ## __VA_ARGS__ ); \
} \
if( !ignored##unique )
#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \
2017-01-24 02:22:15 +01:00
CHECK_ERROR_EX( unique, condition, context, ssphp, ## __VA_ARGS__ )
#define CHECK_ERROR( condition, context, ... ) \
2017-01-24 02:22:15 +01:00
CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, 0, ## __VA_ARGS__ )
#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \
2017-01-24 02:22:15 +01:00
CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ )
#define CHECK_SQL_ERROR( result, context, ... ) \
SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \
2017-01-24 02:22:15 +01:00
CHECK_ERROR( result == SQL_ERROR, context, ## __VA_ARGS__ )
#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \
bool ignored##unique = true; \
if( condition ) { \
2017-01-24 02:22:15 +01:00
ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, ## __VA_ARGS__ ); \
} \
if( !ignored##unique )
#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \
2017-01-24 02:22:15 +01:00
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 ) { \
2017-01-24 02:22:15 +01:00
(void)call_error_handler( context, 0 TSRMLS_CC, /*warning*/ true, ## __VA_ARGS__ ); \
}
#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \
2017-01-24 02:22:15 +01:00
CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ )
#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \
2017-01-24 02:22:15 +01:00
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 ) { \
2017-01-24 02:22:15 +01:00
ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, ##__VA_ARGS__ ); \
} \
else if( result == SQL_SUCCESS_WITH_INFO ) { \
2017-01-24 02:22:15 +01:00
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, ... ) \
2017-01-24 02:22:15 +01:00
(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( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC )
{
// 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( r )) {
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 );
CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) {
throw CoreException();
}
2016-06-13 23:44:53 +02:00
std::size_t driver_version = stmt->conn->driver_version;
if( !strcmp( reinterpret_cast<const char*>( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) {
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( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
_Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
_Out_ 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( SQLSMALLINT HandleType, sqlsrv_context& InputHandle,
_Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr );
CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) {
throw CoreException();
}
}
inline void SQLBindParameter( sqlsrv_stmt* stmt,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
_Inout_ SQLPOINTER ParameterValuePtr,
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 SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier,
2017-01-17 23:19:45 +01:00
_Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length,
_Out_ SQLSMALLINT* out_buffer_length, _Out_ 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();
}
}
2017-01-17 21:38:31 +01:00
inline void SQLColAttributeW( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier,
2017-01-17 23:19:45 +01:00
_Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length,
_Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC )
2017-01-17 21:38:31 +01:00
{
SQLRETURN r = ::SQLColAttributeW( stmt->handle(), field_index, field_identifier, field_type_char,
2017-01-17 23:19:45 +01:00
buffer_length, out_buffer_length, field_type_num );
2017-01-17 21:38:31 +01:00
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length,
_Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size,
_Out_ SQLSMALLINT* decimal_digits, _Out_ 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();
}
}
2017-01-17 21:38:31 +01:00
inline void SQLDescribeColW( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLWCHAR* col_name, SQLSMALLINT col_name_length,
2017-01-17 23:19:45 +01:00
_Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size,
_Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC )
2017-01-17 21:38:31 +01:00
{
SQLRETURN r;
r = ::SQLDescribeColW( stmt->handle(), colno, col_name, col_name_length, col_name_length_out,
2017-01-17 23:19:45 +01:00
data_type, col_size, decimal_digits, nullable );
2017-01-17 21:38:31 +01:00
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, 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( sqlsrv_stmt* stmt, char* sql TSRMLS_DC )
{
SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast<SQLCHAR*>( 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( sqlsrv_stmt* stmt, SQLWCHAR* wsql TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast<SQLWCHAR*>( 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( 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( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, 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( sqlsrv_context& ctx TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {}
}
inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type,
_Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length,
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( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len,
_Out_ 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( sqlsrv_stmt* stmt, 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( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r = ::SQLMoreResults( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLSMALLINT SQLNumResultCols( 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( sqlsrv_stmt* stmt, _Out_ 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( sqlsrv_stmt* stmt, SQLWCHAR * sql, 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( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, 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( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
SQLLEN rows_affected;
r = ::SQLRowCount( stmt->handle(), &rows_affected );
2017-01-26 23:43:15 +01:00
// 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.
2017-01-24 02:22:15 +01:00
#ifdef __linux__
2017-01-26 23:43:15 +01:00
if( r == -1 && rows_affected == -1 )
return 0;
2017-01-24 02:22:15 +01:00
#endif
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return rows_affected;
}
inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, 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 SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, 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( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, 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( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, 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 ***
2016-05-04 05:05:41 +02:00
//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(zval* value_z, const char* str, 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_<zend_function_name> 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( sqlsrv_context& ctx, zval* array, zend_ulong index, 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( sqlsrv_context& ctx, zval* array, 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( sqlsrv_context& ctx, zval* array_z, 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( sqlsrv_context& ctx, zval* array_z, const char* key, 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( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, 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( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC)
{
int zr = ::array_init(new_array);
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, 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(sqlsrv_context& ctx, HashTable* ht, _Out_ 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(sqlsrv_context& ctx, HashTable* ht, _Out_ 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( sqlsrv_context& ctx, HashTable* ht, 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( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, 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(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, 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(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, 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( sqlsrv_context& ctx, HashTable* ht, 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(sqlsrv_context& ctx, HashTable* ht, void* data, 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(sqlsrv_context& ctx, HashTable* ht, 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, HashTable* ht, uint32_t initial_size,
dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC )
{
::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent);
}
inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data,
unsigned int data_size, zval* pDest TSRMLS_DC )
{
int zr = ::zend_hash_add(ht, key, data) != NULL ? SUCCESS : FAILURE;
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
template <typename Statement>
sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC )
{
return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC );
}
template <typename Connection>
sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC )
{
return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC );
}
} // namespace core
2017-01-26 22:54:02 +01:00
// connection attribute functions
template <unsigned int Attr>
struct str_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )),
static_cast<SQLINTEGER>(Z_STRLEN_P( value )) TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
#endif // CORE_SQLSRV_H