2582 lines
101 KiB
C++
2582 lines
101 KiB
C++
#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.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" {
|
|
|
|
#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 <sql.h>
|
|
#include <sqlext.h>
|
|
#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 <deque>
|
|
#include <map>
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <cassert>
|
|
#include <memory>
|
|
#include <vector>
|
|
// included for SQL Server specific constants
|
|
#include "msodbcsql.h"
|
|
|
|
#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;
|
|
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;
|
|
|
|
// identifier for whether or not we have obtained the number of rows and columns
|
|
// of a result
|
|
const short ACTIVE_NUM_COLS_INVALID = -99;
|
|
const long ACTIVE_NUM_ROWS_INVALID = -99;
|
|
|
|
// 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 <bool b>
|
|
struct sqlsrv_static_assert;
|
|
|
|
template <>
|
|
struct sqlsrv_static_assert<true> { _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 <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( _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<void>::const_pointer = 0 )
|
|
{
|
|
return reinterpret_cast<pointer>( 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<size_type>::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 <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[]( _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<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=( _In_opt_ 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( _Inout_opt_ 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( _In_opt_ 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=( _In_opt_ T* ptr )
|
|
{
|
|
return sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >::operator=( ptr );
|
|
}
|
|
|
|
void operator=( _Inout_opt_ 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( _In_ 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( _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<HashTable, hash_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<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( _In_opt_ zval* ptr = NULL )
|
|
{
|
|
if( _ptr )
|
|
zval_ptr_dtor(_ptr );
|
|
_ptr = ptr;
|
|
}
|
|
|
|
zval* operator=( _In_opt_ zval* ptr )
|
|
{
|
|
return sqlsrv_auto_ptr<zval, zval_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<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
|
|
native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_ERROR_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_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>( 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<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( _Inout_opt_ 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( _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<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=( _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<char> akv_id;
|
|
sqlsrv_malloc_auto_ptr<char> 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<ACCESSTOKEN> 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 Language[] = "Language";
|
|
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_LANGUAGE,
|
|
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<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( _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
|
|
short column_count; // Number of columns in the current result set obtained from SQLNumResultCols
|
|
long row_count; // Number of rows in the current result set obtained from SQLRowCount
|
|
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<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
|
|
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_meta_data> param_descriptions;
|
|
|
|
// meta data for current result set
|
|
std::vector<field_meta_data*, sqlsrv_allocator<field_meta_data*>> 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<SQLCHAR> 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_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)( _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<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) { \
|
|
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<char*>( 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<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( _Inout_ sqlsrv_stmt* stmt, _In_ 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( _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_<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( _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 <typename Statement>
|
|
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 <typename Connection>
|
|
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 <unsigned int Attr>
|
|
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<SQLPOINTER>( Z_STRVAL_P( value )), static_cast<SQLINTEGER>( Z_STRLEN_P( value )) TSRMLS_CC );
|
|
}
|
|
catch ( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif // CORE_SQLSRV_H
|