Removing old files

This commit is contained in:
Meet Bhagdev 2016-01-29 17:59:05 -08:00
parent 2765963614
commit 08059fe73b
37 changed files with 0 additions and 30500 deletions

14
LICENSE
View file

@ -1,14 +0,0 @@
Copyright(c) 2015 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.

View file

@ -1,88 +0,0 @@
# Microsoft Drivers for PHP for SQL Server
**Welcome to the Microsoft Drivers for PHP for SQL Server project!!**
The Microsoft Drivers for PHP for SQL Server are PHP 5 extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The release contains two drivers, the SQLSRV driver and the PDO_SQLSRV driver. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including SQL Azure). These drivers rely on the Microsoft ODBC Driver 11 for SQL Server to handle the low-level communication with SQL Server.
Microsoft has published the source code to the driver on this site. With each successive update, we will revisit this plan. We've seen too many projects over-reach in their plans to be responsive to the community. We would prefer to start with a more conservative approach and make sure we're successfully delivering on that before expanding. We understand that many developers will download the source code to create their own build(s) of the driver. However, Microsoft supports only the Microsoft signed versions of the driver.
We hope you enjoy using the Microsoft Drivers for PHP for SQL Server.
The Microsoft Drivers for PHP for SQL Server Team
## Announcements
Please visit the [blog][blog] for more announcements.
## Prerequisites
You must first be able to build PHP without including these
extensions. For help with doing this, see the [official PHP website][phpweb].
## Build
To compile the SQLSRV and PDO_SQLSRV:
1. Copy the source code directories from this repository into the ext
subdirectory.
2. Run buildconf.bat to rebuild the configure.js script to include the
new drivers.
3. Run "cscript configure.js --enable-sqlsrv=shared --enable-pdo
--with-pdo-sqlsrv=shared [other options]" to generate the makefile.
Run "cscript configure.js --help" to see what other options are
available. It is possible (and even probable) that other extensions
will have to be disabled or enabled for the compile to succeed.
Search bing.com for configurations that have worked for other people.
* It might be possible to compile these extensions as non-shared but that configuration has not been tested.
* NB: To build the driver with PHP 5.6.7 and later, you will need to specify the --with-odbcver=0x0380 argument to configure.js
4. Run "nmake". It is suggested that you run the entire build. If you
wish to do so, run "nmake clean" first.
5. To install the resulting build, run "nmake install" or just copy
php_sqlsrv.dll and php_pdo_sqlsrv.dll to your PHP extension directory.
Also enable them within your PHP installation's php.ini file.
This software has been compiled and tested under PHP 5.4.32 and later
using the Visual C++ 2008 and 2012, Express and Standard compilers.
## Documentation
This driver is documented on [Microsoft's Documentation web site][phpdoc].
## Changes
For details about the changes included in this release, please see our [blog][blog] or see the SQLSRV_Readme.htm
file that is part of the download package.
## Known Issues
Please visit the [project on Github][project] to view outstanding [issues][issues].
## Downlod the driver
The driver can be downloaded from the [Microsoft Download Center][link]
## Notes
####Note about version.h
The version numbers in version.h in the source do not match the
version numbers in the supported PHP extension.
## License
The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details.
[blog]: http://blogs.msdn.com/b/sqlphp/
[project]: https://github.com/Azure/msphpsql
[issues]: https://github.com/Azure/msphpsql/issues
[phpweb]: http://php.net
[phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx
[link]: https://www.microsoft.com/en-us/download/details.aspx?id=20098

View file

@ -1 +0,0 @@
Microsoft Drivers 3.2.0 for PHP for SQL Server (PDO driver)

View file

@ -1,39 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: config.w32
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no");
if( PHP_PDO_SQLSRV != "no" ) {
if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") &&
CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")) {
EXTENSION("pdo_sqlsrv", "pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp" )
CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_PDO_SQLSRV_ODBC');
CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_PDO_SQLSRV_ODBC');
ADD_FLAG( 'LDFLAGS_PDO_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' );
ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/EHsc' );
ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/GS' );
ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/Zi' );
ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' );
ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D _HAS_CPP0X=0' );
ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo');
}
}

View file

@ -1,761 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_conn.cpp
//
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <php.h>
#include <psapi.h>
#include <windows.h>
#include <winver.h>
#include <string>
#include <sstream>
// *** internal variables and constants ***
namespace {
// *** internal constants ***
// an arbitrary figure that should be large enough for most connection strings.
const int DEFAULT_CONN_STR_LEN = 2048;
// length of buffer used to retrieve information for client and server info buffers
const int INFO_BUFFER_LEN = 256;
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
// ODBC driver name.
const char CONNECTION_STRING_DRIVER_NAME[] = "Driver={ODBC Driver 11 for SQL Server};";
// default options if only the server is specified
const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}";
// connection option appended when no user name or password is given
const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};";
// connection option appended for MARS when MARS isn't explicitly mentioned
const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};";
// *** internal function prototypes ***
void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
HashTable* options_ht, const connection_option valid_conn_opts[],
void* driver,__inout std::string& connection_string TSRMLS_DC );
void determine_server_version( sqlsrv_conn* conn TSRMLS_DC );
const char* get_processor_arch( void );
void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC );
connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, unsigned int key_len TSRMLS_DC );
void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC );
}
// core_sqlsrv_connect
// opens a connection and returns a sqlsrv_conn structure.
// Parameters:
// henv_cp - connection pooled env context
// henv_ncp - non connection pooled env context
// server - name of the server we're connecting to
// uid - username
// pwd - password
// options_ht - zend_hash list of options
// err - error callback to put into the connection's context
// valid_conn_opts[] - array of valid driver supported connection options.
// driver - reference to caller
// Return
// A sqlsrv_conn structure. An exception is thrown if an error occurs
sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
const char* server, const char* uid, const char* pwd,
HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[],
void* driver, const char* driver_func TSRMLS_DC )
{
SQLRETURN r;
std::string conn_str;
conn_str.reserve( DEFAULT_CONN_STR_LEN );
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
sqlsrv_malloc_auto_ptr<wchar_t> wconn_string;
unsigned int wconn_len = 0;
try {
sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv
// check the connection pooling setting to determine which henv to use to allocate the connection handle
// we do this earlier because we have to allocate the connection handle prior to setting attributes on
// it in build_connection_string_and_set_conn_attr.
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
zval** option_zz = NULL;
int zr = SUCCESS;
zr = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING, reinterpret_cast<void**>( &option_zz ));
if( zr != FAILURE ) {
// if the option was found and it's not true, then use the non pooled environment handle
if(( Z_TYPE_PP( option_zz ) == IS_STRING && !core_str_zval_is_true( *option_zz )) || !zend_is_true( *option_zz ) ) {
henv = &henv_ncp;
}
}
}
SQLHANDLE temp_conn_h;
core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC );
conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC );
conn->set_func( driver_func );
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver,
conn_str TSRMLS_CC );
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
wconn_len = (conn_str.length() + 1) * sizeof( wchar_t );
wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), conn_str.length(), &wconn_len );
CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message() )
{
throw core::CoreException();
}
SQLSMALLINT output_conn_size;
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get() ),
static_cast<SQLSMALLINT>( wconn_len ), NULL,
0, &output_conn_size, SQL_DRIVER_NOPROMPT );
// clear the connection string from memory to remove sensitive data (such as a password).
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if( !SQL_SUCCEEDED( r )) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// if it's a IM002, meaning that the correct ODBC driver is not installed
CHECK_CUSTOM_ERROR( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' &&
state[4] == '2', conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) {
throw core::CoreException();
}
}
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
determine_server_version( conn TSRMLS_CC );
}
catch( std::bad_alloc& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn->invalidate();
DIE( "C++ memory allocation failure building the connection string." );
}
catch( std::out_of_range const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( std::length_error const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( core::CoreException& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn->invalidate();
throw;
}
sqlsrv_conn* return_conn = conn;
conn.transferred();
return return_conn;
}
// core_sqlsrv_begin_transaction
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
// the call to core_sqlsrv_begin_transaction and before any calls to
// core_sqlsrv_rollback or core_sqlsrv_commit.
// The default transaction mode is auto-commit. This means that all queries
// are automatically committed upon success unless they have been designated
// as part of an explicit transaction by using core_sqlsrv_begin_transaction.
// Parameters:
// sqlsrv_conn*: The connection with which the transaction is associated.
void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_commit
// Commits the current transaction on the specified connection and returns the
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
// core_sqlsrv_commit.
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_rollback
// Rolls back the current transaction on the specified connection and returns
// the connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
// core_sqlsrv_commit.
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_close
// Called when a connection resource is destroyed by the Zend engine.
// Parameters:
// conn - The current active connection.
void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC )
{
// if the connection wasn't successful, just return.
if( conn == NULL )
return;
try {
// rollback any transaction in progress (we don't care about the return result)
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
}
catch( core::CoreException& ) {
LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." );
}
// disconnect from the server
SQLRETURN r = SQLDisconnect( conn->handle() );
if( !SQL_SUCCEEDED( r )) {
LOG( SEV_ERROR, "Disconnect failed when closing the connection." );
}
// free the connection handle
conn->invalidate();
sqlsrv_free( conn );
}
// core_sqlsrv_prepare
// Create a statement object and prepare the SQL query passed in for execution at a later time.
// Parameters:
// stmt - statement to be prepared
// sql - T-SQL command to prepare
// sql_len - length of the T-SQL string
void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC )
{
try {
// convert the string from its encoding to UTf-16
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
sqlsrv_malloc_auto_ptr<wchar_t> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( sizeof( wchar_t )));
wsql_string[0] = L'\0';
wsql_len = 0;
}
else {
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
stmt->encoding() );
wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast<const char*>( sql ),
sql_len, &wsql_len );
CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
get_last_error_message() ) {
throw core::CoreException();
}
}
// prepare our wide char query string
core::SQLPrepareW( stmt, reinterpret_cast<SQLWCHAR*>( wsql_string.get() ), wsql_len TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_get_server_version
// Determines the vesrion of the SQL Server we are connected to. Calls a helper function
// get_server_version to get the version of SQL Server.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// *server_version - zval for returning results.
void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
ZVAL_STRINGL( server_version, buffer, buffer_len, 0 );
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_get_server_info
// Returns the Database name, the name of the SQL Server we are connected to
// and the version of the SQL Server.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// *server_info - zval for returning results.
void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
// initialize the array
core::sqlsrv_array_init( *conn, server_info TSRMLS_CC );
// Get the database name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the server version
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the server name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_get_client_info
// Returns the ODBC driver's dll name, version and the ODBC version.
// Parameters
// conn - The connection resource by which the client and server are connected.
// *client_info - zval for returning the results.
void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
// initialize the array
core::sqlsrv_array_init( *conn, client_info TSRMLS_CC );
// Get the ODBC driver's dll name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the ODBC driver's ODBC version
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the OBDC driver's version
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// core_is_conn_opt_value_escaped
// determine if connection string value is properly escaped.
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
// the value will be surrounded by { and } by the caller after it has been validated
bool core_is_conn_opt_value_escaped( const char* value, int value_len )
{
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// unquoted since we quote it when adding it to the connection string.
if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
++value;
value_len -= 2;
}
// check to make sure that all right braces are escaped
int i = 0;
while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) {
// skip both braces
if( value[i] == '}' )
++i;
++i;
}
if( i < value_len && value[i] == '}' ) {
return false;
}
return true;
}
// *** internal connection functions and classes ***
namespace {
connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long key,
const connection_option conn_opts[] TSRMLS_DC )
{
for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) {
if( key == conn_opts[ opt_idx ].conn_option_key ) {
return &conn_opts[ opt_idx ];
}
}
SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." );
return NULL; // avoid a compiler warning
}
// says what it does, and does what it says
// rather than have attributes and connection strings as ODBC does, we unify them into a hash table
// passed to the connection, and then break them out ourselves and either set attributes or put the
// option in the connection string.
void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
HashTable* options, const connection_option valid_conn_opts[],
void* driver,__inout std::string& connection_string TSRMLS_DC )
{
bool credentials_mentioned = false;
bool mars_mentioned = false;
connection_option const* conn_opt;
int zr = SUCCESS;
try {
connection_string = CONNECTION_STRING_DRIVER_NAME;
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC );
// if uid is not present then we use trusted connection.
if(uid == NULL || strlen( uid ) == 0 ) {
connection_string += "Trusted_Connection={Yes};";
}
else {
bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC );
// if no password was given, then don't add a password to the connection string. Perhaps the UID
// given doesn't have a password?
if( pwd != NULL ) {
escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC );
}
}
// if no options were given, then we set MARS the defaults and return immediately.
if( options == NULL || zend_hash_num_elements( options ) == 0 ) {
connection_string += CONNECTION_STRING_DEFAULT_OPTIONS;
return;
}
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
// if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn"
// flag is set to false.
if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) {
zval** trace_value = NULL;
int zr = zend_hash_index_find( options, SQLSRV_CONN_OPTION_TRACE_ON, (void**)&trace_value );
if( zr == FAILURE || !zend_is_true( *trace_value )) {
zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE );
}
}
for( zend_hash_internal_pointer_reset( options );
zend_hash_has_more_elements( options ) == SUCCESS;
zend_hash_move_forward( options )) {
int type = HASH_KEY_NON_EXISTANT;
char *key = NULL;
unsigned int key_len = -1;
unsigned long index = -1;
zval** data = NULL;
type = zend_hash_get_current_key_ex( options, &key, &key_len, &index, 0, NULL );
// The driver layer should ensure a valid key.
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." );
core::sqlsrv_zend_hash_get_current_data( *conn, options, (void**) &data TSRMLS_CC );
conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC );
if( index == SQLSRV_CONN_OPTION_MARS ) {
mars_mentioned = true;
}
conn_opt->func( conn_opt, *data, conn, connection_string TSRMLS_CC );
}
// MARS on if not explicitly turned off
if( !mars_mentioned ) {
connection_string += CONNECTION_OPTION_MARS_ON;
}
}
catch( core::CoreException& ) {
throw;
}
}
// get_server_version
// Helper function which returns the version of the SQL Server we are connected to.
void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
*server_version = buffer;
len = buffer_len;
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// get_processor_arch
// Calls GetSystemInfo to verify the what architecture of the processor is supported
// and return the string of the processor name.
const char* get_processor_arch( void )
{
SYSTEM_INFO sys_info;
GetSystemInfo( &sys_info);
switch( sys_info.wProcessorArchitecture ) {
case PROCESSOR_ARCHITECTURE_INTEL:
return PROCESSOR_ARCH[0];
case PROCESSOR_ARCHITECTURE_AMD64:
return PROCESSOR_ARCH[1];
case PROCESSOR_ARCHITECTURE_IA64:
return PROCESSOR_ARCH[2];
default:
DIE( "Unknown Windows processor architecture." );
return NULL;
}
}
// some features require a server of a certain version or later
// this function determines the version of the server we're connected to
// and stores it in the connection. Any errors are logged before return.
// Exception is thrown when the server version is either undetermined
// or is invalid (< 2000).
void determine_server_version( sqlsrv_conn* conn TSRMLS_DC )
{
SQLSMALLINT info_len;
char p[ INFO_BUFFER_LEN ];
core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC );
errno = 0;
char version_major_str[ 3 ];
SERVER_VERSION version_major;
memcpy( version_major_str, p, 2 );
version_major_str[ 2 ] = '\0';
version_major = static_cast<SERVER_VERSION>( atoi( version_major_str ));
CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION )
{
throw core::CoreException();
}
// SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least
// that high
conn->server_version = version_major;
}
void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC )
{
// wrap a connection option in a quote. It is presumed that any character that need to be escaped will
// be escaped, such as a closing }.
TSRMLS_C;
if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) {
++val;
val_len -= 2;
}
conn_str += odbc_name;
conn_str += "={";
conn_str.append( val, val_len );
conn_str += "};";
}
} // namespace
// simply add the parsed value to the connection string
void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str
TSRMLS_DC )
{
const char* val_str = Z_STRVAL_P( value );
int val_len = Z_STRLEN_P( value );
common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC );
}
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC )
{
TSRMLS_C;
}
// helper function to evaluate whether a string value is true or false.
// Values = ("true" or "1") are treated as true values. Everything else is treated as false.
// Returns 1 for true and 0 for false.
int core_str_zval_is_true( zval* value_z )
{
SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." );
char* value_in = Z_STRVAL_P( value_z );
int val_len = Z_STRLEN_P( value_z );
// strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
int last_char = val_len - 1;
while( isspace( value_in[ last_char ] )) {
value_in[ last_char ] = '\0';
val_len = last_char;
--last_char;
}
// save adjustments to the value made by stripping whitespace at the end
ZVAL_STRINGL( value_z, value_in, val_len, 0 );
const char VALID_TRUE_VALUE_1[] = "true";
const char VALID_TRUE_VALUE_2[] = "1";
if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) ||
( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len ))
) {
return 1; // true
}
return 0; // false
}

View file

@ -1,173 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_init.cpp
//
// Contents: common initialization routines shared by PDO and sqlsrv
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
// module global variables (initialized in minit and freed in mshutdown)
HMODULE g_sqlsrv_hmodule = NULL;
OSVERSIONINFO g_osversion;
// core_sqlsrv_minit
// Module initialization
// This function is called once per execution by the driver layer's MINIT function.
// The primary responsibility of this function is to allocate the two environment
// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC
// connection handle.
// Parameters:
// henv_cp - Environment handle for pooled connection.
// henv_ncp - Environment handle for non-pooled connection.
// err - Driver specific error handler which handles any errors during initialization.
void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC )
{
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long ));
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long ));
*henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL
try {
// get the version of the OS we're running on. For now this governs certain flags used by
// WideCharToMultiByte. It might be relevant to other things in the future.
g_osversion.dwOSVersionInfoSize = sizeof( g_osversion );
BOOL ver_return = GetVersionEx( &g_osversion );
if( !ver_return ) {
LOG( SEV_ERROR, "Failed to retrieve Windows version information." );
throw core::CoreException();
}
SQLHANDLE henv = SQL_NULL_HANDLE;
SQLRETURN r;
// allocate the non pooled environment handle
// we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so
// we use the direct ODBC function.
r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
if( !SQL_SUCCEEDED( r )) {
throw core::CoreException();
}
*henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL );
(*henv_ncp)->set_func( driver_func );
// set to ODBC 3
core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER
TSRMLS_CC );
// disable connection pooling
core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>( SQL_CP_OFF ),
SQL_IS_UINTEGER TSRMLS_CC );
// allocate the pooled envrionment handle
// we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so
// we use the direct ODBC function.
r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
if( !SQL_SUCCEEDED( r )) {
throw core::CoreException();
}
*henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL );
(*henv_cp)->set_func( driver_func );
// set to ODBC 3
core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC);
// enable connection pooling
core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>( SQL_CP_ONE_PER_HENV ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& e ) {
LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." );
if( *henv_ncp != NULL ) {
// free the ODBC env handle allocated just above
SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp );
delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap)
*henv_ncp = NULL;
}
if( *henv_cp != NULL ) {
// free the ODBC env handle allocated just above
SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp );
delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap)
*henv_cp = NULL;
}
throw e; // rethrow for the driver to catch
}
catch( std::bad_alloc& e ) {
LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." );
if( *henv_ncp != NULL ) {
SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp );
delete *henv_ncp;
*henv_ncp = NULL;
}
if( *henv_cp ) {
SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp );
delete *henv_cp;
*henv_cp = NULL;
}
throw e; // rethrow for the driver to catch
}
}
// core_sqlsrv_mshutdown
// Module shutdown function
// Free the environment handles allocated in MINIT and unregister our stream wrapper.
// Resource types and constants are automatically released since we don't flag them as
// persistent when they are registered.
// Parameters:
// henv_cp - Pooled environment handle.
// henv_ncp - Non-pooled environment handle.
void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp )
{
if( henv_ncp != SQL_NULL_HANDLE ) {
henv_ncp.invalidate();
}
if( henv_cp != SQL_NULL_HANDLE ) {
henv_cp.invalidate();
}
return;
}
// DllMain for the extension.
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID )
{
switch( fdwReason ) {
case DLL_PROCESS_ATTACH:
// store the module handle for use by client_info and server_info
g_sqlsrv_hmodule = hinstDLL;
break;
default:
break;
}
return TRUE;
}

View file

@ -1,1339 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_results.cpp
//
// Contents: Result sets
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <functional>
#include <iostream>
#include <sstream>
using namespace core;
// conversion matrix
// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported
// this is initialized the first time the buffered result set is created.
sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix;
namespace {
// *** internal types ***
#pragma warning(disable:4200)
// *** internal constants ***
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
// *** internal functions ***
// return an integral type rounded up to a certain number
template <int align, typename T>
T align_to( T number )
{
DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" );
return ((number % align) == 0) ? number : (number + align - (number % align));
}
// return a pointer address aligned to a certain address boundary
template <int align, typename T>
T* align_to( T* ptr )
{
size_t p_value = (size_t) ptr;
return align_to<align, size_t>( p_value );
}
// set the nth bit of the bitstream starting at ptr
void set_bit( void* ptr, unsigned int bit )
{
unsigned char* null_bits = reinterpret_cast<unsigned char*>( ptr );
null_bits += bit >> 3;
*null_bits |= 1 << ( 7 - ( bit & 0x7 ));
}
// retrieve the nth bit from the bitstream starting at ptr
bool get_bit( void* ptr, unsigned int bit )
{
unsigned char* null_bits = reinterpret_cast<unsigned char*>( ptr );
null_bits += bit >> 3;
return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0);
}
// read in LOB field during buffered result creation
SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
unsigned long mem_used TSRMLS_DC );
// dtor for each row in the cache
void cache_row_dtor( void* data );
// convert a number to a string using locales
// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of
// subclassing a new streambuf just to avoid the copy, it's easier to do the copy
template <typename Char, typename Number>
SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
sqlsrv_error_auto_ptr& last_error )
{
std::basic_ostringstream<Char> os;
std::locale loc;
os.imbue( loc );
std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream<Char>::_Iter( os.rdbuf() ), os, ' ', *number_data );
std::basic_string<Char>& str_num = os.str();
if( os.fail() ) {
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
(SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 );
return SQL_ERROR;
}
if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) {
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
(SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 );
return SQL_ERROR;
}
*out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator
memcpy( buffer, str_num.c_str(), *out_buffer_length );
return SQL_SUCCESS;
}
template <typename Number, typename Char>
SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error )
{
Number* number_data = reinterpret_cast<Number*>( buffer );
std::locale loc; // default locale should match system
std::basic_istringstream<Char> is;
is.str( string_data );
is.imbue( loc );
std::ios_base::iostate st = 0;
std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream<Char>::_Iter( is.rdbuf( ) ),
std::basic_istream<Char>::_Iter(0), is, st, *number_data );
if( st & std::ios_base::failbit ) {
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
(SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 );
return SQL_ERROR;
}
*out_buffer_length = sizeof( Number );
return SQL_SUCCESS;
}
// "closure" for the hash table destructor
struct row_dtor_closure {
sqlsrv_buffered_result_set* results;
BYTE* row_data;
row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) :
results( st ), row_data( row )
{
}
};
sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number )
{
SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ];
SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLINTEGER native_code;
SQLSMALLINT wnative_message_len = 0;
SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message,
SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len );
if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
return NULL;
}
// convert the error into the encoding of the context
SQLSRV_ENCODING enc = odbc->encoding();
if( enc == SQLSRV_ENCODING_DEFAULT ) {
enc = odbc->conn->encoding();
}
// convert the error into the encoding of the context
sqlsrv_malloc_auto_ptr<SQLCHAR> sql_state;
SQLINTEGER sql_state_len = 0;
if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) {
return NULL;
}
sqlsrv_malloc_auto_ptr<SQLCHAR> native_message;
SQLINTEGER native_message_len = 0;
if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) {
return NULL;
}
return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message,
native_code );
}
} // namespace
// base class result set
sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) :
odbc( stmt )
{
}
// ODBC result set
// This object simply wraps ODBC function calls
sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) :
sqlsrv_result_set( stmt )
{
}
sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void )
{
}
SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC );
}
SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC );
}
SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length,
out_buffer_length TSRMLS_CC );
}
sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return odbc_get_diag_rec( odbc, record_number );
}
SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLRowCount( odbc TSRMLS_CC );
}
// Buffered result set
// This class holds a result set in memory
sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) :
sqlsrv_result_set( stmt ),
cache(NULL),
col_count(0),
meta(NULL),
current(0),
last_field_index(-1),
read_so_far(0)
{
// 10 is an arbitrary number for now for the initial size of the cache
ALLOC_HASHTABLE( cache );
core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, NULL /* hashfn */, cache_row_dtor /*dtor*/, 0 /*persistent*/
TSRMLS_CC );
col_count = core::SQLNumResultCols( stmt TSRMLS_CC );
// there is no result set to buffer
if( col_count == 0 ) {
return;
}
SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags
meta = static_cast<sqlsrv_buffered_result_set::meta_data*>( sqlsrv_malloc( col_count *
sizeof( sqlsrv_buffered_result_set::meta_data )));
// set up the conversion matrix if this is the first time we're called
if( conv_matrix.size() == 0 ) {
conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string;
conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double;
conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long;
conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string;
conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string;
conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double;
conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string;
conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string;
}
// get the meta data and calculate the size of a row buffer
SQLULEN offset = null_bytes;
for( SQLSMALLINT i = 0; i < col_count; ++i ) {
core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC );
offset = align_to<4>( offset );
meta[i].offset = offset;
switch( meta[i].type ) {
// these types are the display size
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL,
reinterpret_cast<SQLLEN*>( &meta[i].length ) TSRMLS_CC );
meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space
offset += meta[i].length;
break;
// these types are the column size
case SQL_BINARY:
case SQL_CHAR:
case SQL_SS_UDT:
case SQL_VARBINARY:
case SQL_VARCHAR:
// var* field types are length prefixed
if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
offset += sizeof( void* );
}
else {
meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space
offset += meta[i].length;
}
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
offset += sizeof( void* );
}
else {
meta[i].length *= sizeof( WCHAR );
meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space
offset += meta[i].length;
}
break;
// these types are LOBs
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN;
offset += sizeof( void* );
break;
// these types are the ISO date size
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_TYPE_TIMESTAMP:
core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL,
reinterpret_cast<SQLLEN*>( &meta[i].length ) TSRMLS_CC );
meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space
offset += meta[i].length;
break;
// these types are the native size
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
meta[i].length = sizeof( long );
offset += meta[i].length;
break;
case SQL_REAL:
case SQL_FLOAT:
meta[i].length = sizeof( double );
offset += meta[i].length;
break;
default:
SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" );
break;
}
switch( meta[i].type ) {
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DATETIME:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_LONGVARCHAR:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_XML:
case SQL_TYPE_TIMESTAMP:
case SQL_VARCHAR:
meta[i].c_type = SQL_C_CHAR;
break;
case SQL_SS_UDT:
case SQL_LONGVARBINARY:
case SQL_BINARY:
case SQL_VARBINARY:
meta[i].c_type = SQL_C_BINARY;
break;
case SQL_WLONGVARCHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
meta[i].c_type = SQL_C_WCHAR;
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
meta[i].c_type = SQL_C_LONG;
break;
case SQL_REAL:
case SQL_FLOAT:
meta[i].c_type = SQL_C_DOUBLE;
break;
default:
SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" );
break;
}
}
// read the data into the cache
// (offset from the above loop has the size of the row buffer necessary)
unsigned long mem_used = 0;
unsigned long row_count = 0;
while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) {
// allocate the row buffer
unsigned char* row = static_cast<unsigned char*>( sqlsrv_malloc( offset ));
memset( row, 0, offset );
// read the fields into the row buffer
for( SQLSMALLINT i = 0; i < col_count; ++i ) {
SQLLEN out_buffer_temp = SQL_NULL_DATA;
SQLPOINTER buffer;
SQLLEN* out_buffer_length = &out_buffer_temp;
switch( meta[i].c_type ) {
case SQL_C_CHAR:
case SQL_C_WCHAR:
case SQL_C_BINARY:
if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
out_buffer_length = &out_buffer_temp;
SQLPOINTER* lob_addr = reinterpret_cast<SQLPOINTER*>( &row[ meta[i].offset ] );
*lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC );
// a NULL pointer means NULL field
if( *lob_addr == NULL ) {
*out_buffer_length = SQL_NULL_DATA;
}
else {
*out_buffer_length = **reinterpret_cast<SQLLEN**>( lob_addr );
mem_used += *out_buffer_length;
}
}
else {
mem_used += meta[i].length;
CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
buffer = row + meta[i].offset + sizeof( SQLULEN );
out_buffer_length = reinterpret_cast<SQLLEN*>( row + meta[i].offset );
core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
false TSRMLS_CC );
}
break;
case SQL_C_LONG:
case SQL_C_DOUBLE:
{
mem_used += meta[i].length;
CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
buffer = row + meta[i].offset;
out_buffer_length = &out_buffer_temp;
core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
false TSRMLS_CC );
}
break;
default:
SQLSRV_ASSERT( false, "Unknown C type" );
break;
}
if( *out_buffer_length == SQL_NULL_DATA ) {
unsigned char* null_bits = reinterpret_cast<unsigned char*>( row );
set_bit( row, i );
}
}
SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" );
// add it to the cache
row_dtor_closure cl( this, row );
sqlsrv_zend_hash_next_index_insert( *stmt, cache, &cl, sizeof( cl ) TSRMLS_CC );
}
}
sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void )
{
// free the rows
if( cache ) {
zend_hash_destroy( cache );
FREE_HASHTABLE( cache );
cache = NULL;
}
// free the meta data
if( meta ) {
efree( meta );
meta = NULL;
}
}
SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC )
{
last_error = NULL;
last_field_index = -1;
read_so_far = 0;
switch( orientation ) {
case SQL_FETCH_NEXT:
offset = 1;
orientation = SQL_FETCH_RELATIVE;
break;
case SQL_FETCH_PRIOR:
offset = -1;
orientation = SQL_FETCH_RELATIVE;
break;
}
switch( orientation ) {
case SQL_FETCH_FIRST:
current = 1;
break;
case SQL_FETCH_LAST:
current = row_count( TSRMLS_C );
break;
case SQL_FETCH_ABSOLUTE:
current = offset;
break;
case SQL_FETCH_RELATIVE:
current += offset;
break;
default:
SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." );
break;
}
// check validity of current row
// the cursor can never get further away than just before the first row
if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) {
current = 0;
return SQL_NO_DATA;
}
// the cursor can never get further away than just after the last row
if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) {
current = row_count( TSRMLS_C ) + 1;
return SQL_NO_DATA;
}
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )
{
last_error = NULL;
field_index--; // convert from 1 based to 0 based
SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" );
if( field_index != last_field_index ) {
last_field_index = field_index;
read_so_far = 0;
}
unsigned char* row = get_row();
// if the field is null, then return SQL_NULL_DATA
if( get_bit( row, field_index )) {
*out_buffer_length = SQL_NULL_DATA;
return SQL_SUCCESS;
}
// check to make sure the conversion type is valid
if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() ||
conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) ==
conv_matrix.find( meta[ field_index ].c_type )->second.end() ) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "07006",
(SQLCHAR*) "Restricted data type attribute violation", 0 );
return SQL_ERROR;
}
return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length,
out_buffer_length );
}
SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC )
{
SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" );
SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE,
"Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" );
SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE,
"Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" );
if( last_error == NULL ) {
return SQL_NO_DATA;
}
SQLSRV_ASSERT( last_error->sqlstate != NULL,
"Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" );
memcpy( diag_info_buffer, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE ));
return SQL_SUCCESS;
}
unsigned char* sqlsrv_buffered_result_set::get_row( void )
{
row_dtor_closure* cl_ptr;
int zr = zend_hash_index_find( cache, current - 1, (void**) &cl_ptr );
SQLSRV_ASSERT( zr == SUCCESS, "Failed to find row %1!d! in the cache", current );
return cl_ptr->row_data;
}
sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number )
{
// we only hold a single error if there is one, otherwise return the ODBC error(s)
if( last_error == NULL ) {
return odbc_get_diag_rec( odbc, record_number );
}
if( record_number > 1 ) {
return NULL;
}
return new (sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code );
}
SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D )
{
last_error = NULL;
return zend_hash_num_elements( cache );
}
// private functions
template <typename Char>
SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out void* buffer,
SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
sqlsrv_error_auto_ptr& out_error )
{
// hex characters for the conversion loop below
static char hex_chars[] = "0123456789ABCDEF";
SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" );
SQLRETURN r = SQL_ERROR;
// Set the amount of space necessary for null characters at the end of the data.
SQLSMALLINT extra = sizeof(Char);
SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or "
"multiple of 4 for binary to wide string" );
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
// *2 is for each byte to hex conversion and * extra is for either system or wide string allocation
*out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra;
// copy as much as we can into the buffer
SQLLEN to_copy;
if( buffer_length < *out_buffer_length + extra ) {
to_copy = (buffer_length - extra);
out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
r = SQL_SUCCESS;
to_copy = *out_buffer_length;
}
// if there are bytes to copy as hex
if( to_copy > 0 ) {
// quick hex conversion routine
Char* h = reinterpret_cast<Char*>( buffer );
BYTE* b = reinterpret_cast<BYTE*>( field_data );
// to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
// to get the number of hex digits we can copy
SQLLEN to_copy_hex = to_copy / (2 * extra);
for( int i = 0; i < to_copy_hex; ++i ) {
*h = hex_chars[ (*b & 0xf0) >> 4 ];
h++;
*h = hex_chars[ (*b++ & 0x0f) ];
h++;
}
read_so_far += to_copy_hex;
*h = static_cast<Char>( 0 );
}
else {
reinterpret_cast<char*>( buffer )[0] = '\0';
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLCHAR* row = get_row();
SQLCHAR* field_data = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
}
else {
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
}
return binary_to_string<char>( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLCHAR* row = get_row();
SQLCHAR* field_data = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
}
else {
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
}
return binary_to_string<WCHAR>( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" );
SQLSRV_ASSERT( buffer_length >= sizeof(LONG), "Buffer length must be able to find a long in "
"sqlsrv_buffered_result_set::double_to_long" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
LONG* long_data = reinterpret_cast<LONG*>( buffer );
if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003",
(SQLCHAR*) "Numeric value out of range", 0 );
return SQL_ERROR;
}
if( *double_data != floor( *double_data )) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07",
(SQLCHAR*) "Fractional truncation", 0 );
return SQL_SUCCESS_WITH_INFO;
}
*long_data = static_cast<LONG>( *double_data );
*out_buffer_length = sizeof( LONG );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
return number_to_string<char>( double_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
return number_to_string<WCHAR>( double_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" );
SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( buffer );
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
*double_data = static_cast<LONG>( *long_data );
*out_buffer_length = sizeof( double );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" );
unsigned char* row = get_row();
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
return number_to_string<char>( long_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" );
unsigned char* row = get_row();
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
return number_to_string<WCHAR>( long_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" );
SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" );
unsigned char* row = get_row();
char* string_data = reinterpret_cast<char*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
return string_to_number<double>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" );
SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" );
unsigned char* row = get_row();
SQLWCHAR* string_data = reinterpret_cast<SQLWCHAR*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
return string_to_number<double>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" );
SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" );
unsigned char* row = get_row();
char* string_data = reinterpret_cast<char*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
return string_to_number<LONG>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" );
SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" );
unsigned char* row = get_row();
SQLWCHAR* string_data = reinterpret_cast<SQLWCHAR*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
return string_to_number<LONG>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" );
SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" );
SQLRETURN r = SQL_ERROR;
unsigned char* row = get_row();
SQLCHAR* field_data = NULL;
SQLULEN field_len = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_len = **reinterpret_cast<SQLLEN**>( &row[ meta[ field_index ].offset ] );
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far;
}
else {
field_len = *reinterpret_cast<SQLLEN*>( &row[ meta[ field_index ].offset ] );
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far;
}
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
*out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR);
// to_copy is the number of characters to copy, not including the null terminator
// supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair.
SQLLEN to_copy;
if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) {
to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
r = SQL_SUCCESS;
to_copy = field_len - read_so_far;
}
if( to_copy > 0 ) {
bool tried_again = false;
do {
int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, to_copy,
(LPWSTR) buffer, to_copy );
if( ch_space == 0 ) {
switch( GetLastError() ) {
case ERROR_NO_UNICODE_TRANSLATION:
// the theory here is the conversion failed because the end of the buffer we provided contained only
// half a character at the end
if( !tried_again ) {
to_copy--;
tried_again = true;
continue;
}
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
break;
default:
SQLSRV_ASSERT( false, "Severe error translating Unicode" );
break;
}
return SQL_ERROR;
}
((WCHAR*)buffer)[ to_copy ] = L'\0';
read_so_far += to_copy;
break;
} while( true );
}
else {
reinterpret_cast<WCHAR*>( buffer )[0] = L'\0';
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" );
SQLRETURN r = SQL_ERROR;
unsigned char* row = get_row();
// Set the amount of space necessary for null characters at the end of the data.
SQLSMALLINT extra = 0;
switch( meta[ field_index ].c_type ) {
case SQL_C_WCHAR:
extra = sizeof( SQLWCHAR );
break;
case SQL_C_BINARY:
extra = 0;
break;
case SQL_C_CHAR:
extra = sizeof( SQLCHAR );
break;
default:
SQLSRV_ASSERT( false, "Invalid type in get_string_data" );
break;
}
SQLCHAR* field_data = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
}
else {
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
}
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
*out_buffer_length = *reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far;
// copy as much as we can into the buffer
SQLLEN to_copy;
if( buffer_length < *out_buffer_length + extra ) {
to_copy = buffer_length - extra;
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
r = SQL_SUCCESS;
to_copy = *out_buffer_length;
}
SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" );
if( to_copy > 0 ) {
memcpy( buffer, field_data + read_so_far, to_copy );
read_so_far += to_copy;
}
if( extra ) {
OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
memcpy( reinterpret_cast<SQLCHAR*>( buffer ) + to_copy, L"\0", extra );
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" );
SQLRETURN r = SQL_ERROR;
unsigned char* row = get_row();
SQLCHAR* field_data = NULL;
SQLULEN field_len = NULL;
// if this is the first time called for this field, just convert the entire string to system first then
// use that to read from instead of converting chunk by chunk. This is because it's impossible to know
// the total length of the string for output_buffer_length without doing the conversion and returning
// SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and
// to_same_string).
if( read_so_far == 0 ) {
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_len = **reinterpret_cast<SQLLEN**>( &row[ meta[ field_index ].offset ] );
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far;
}
else {
field_len = *reinterpret_cast<SQLLEN*>( &row[ meta[ field_index ].offset ] );
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far;
}
BOOL default_char_used = FALSE;
char default_char = '?';
// allocate enough to handle WC -> DBCS conversion if it happens
temp_string = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( field_len, sizeof( char ), sizeof(char)));
temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, field_len / sizeof(WCHAR),
(LPSTR) temp_string.get(), field_len, &default_char, &default_char_used );
if( temp_length == 0 ) {
switch( GetLastError() ) {
case ERROR_NO_UNICODE_TRANSLATION:
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
break;
default:
SQLSRV_ASSERT( false, "Severe error translating Unicode" );
break;
}
return SQL_ERROR;
}
}
*out_buffer_length = (temp_length - read_so_far);
SQLLEN to_copy = 0;
if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) {
to_copy = buffer_length - sizeof(char);
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
to_copy = (temp_length - read_so_far);
r = SQL_SUCCESS;
}
if( to_copy > 0 ) {
memcpy( buffer, temp_string.get() + read_so_far, to_copy );
}
SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" );
OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" );
((SQLCHAR*) buffer)[ to_copy ] = '\0';
read_so_far += to_copy;
return r;
}
SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
return to_same_string( field_index, buffer, buffer_length, out_buffer_length );
}
SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" );
SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this
unsigned char* row = get_row();
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
memcpy( buffer, long_data, sizeof( LONG ));
*out_buffer_length = sizeof( LONG );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" );
SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
memcpy( buffer, double_data, sizeof( double ));
*out_buffer_length = sizeof( double );
return SQL_SUCCESS;
}
namespace {
// called for each row in the cache when the cache is destroyed in the destructor
void cache_row_dtor( void* data )
{
row_dtor_closure* cl = reinterpret_cast<row_dtor_closure*>( data );
BYTE* row = cl->row_data;
// don't release this here, since this is called from the destructor of the result_set
sqlsrv_buffered_result_set* result_set = cl->results;
for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) {
if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
void* out_of_row_data = *reinterpret_cast<void**>( &row[ result_set->col_meta_data(i).offset ] );
sqlsrv_free( out_of_row_data );
}
}
sqlsrv_free( row );
}
SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
unsigned long mem_used TSRMLS_DC )
{
SQLSMALLINT extra = 0;
SQLLEN* output_buffer_len = NULL;
// Set the amount of space necessary for null characters at the end of the data.
switch( meta.c_type ) {
case SQL_C_WCHAR:
extra = sizeof( SQLWCHAR );
break;
case SQL_C_BINARY:
extra = 0;
break;
case SQL_C_CHAR:
extra = sizeof( SQLCHAR );
break;
default:
SQLSRV_ASSERT( false, "Invalid type in read_lob_field" );
break;
}
SQLLEN already_read = 0;
SQLLEN to_read = INITIAL_FIELD_STRING_LEN;
sqlsrv_malloc_auto_ptr<char> buffer;
buffer = static_cast<char*>( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN )));
SQLRETURN r = SQL_SUCCESS;
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLLEN last_field_len = 0;
bool full_length_returned = false;
do {
output_buffer_len = reinterpret_cast<SQLLEN*>( buffer.get() );
r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ),
to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC );
// if the field is NULL, then return a NULL pointer
if( last_field_len == SQL_NULL_DATA ) {
return NULL;
}
// if the last read was successful, we're done
if( r == SQL_SUCCESS ) {
// check to make sure we haven't overflown our memory limit
CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
break;
}
// else if it wasn't the truncated warning (01004) then we're done
else if( r == SQL_SUCCESS_WITH_INFO ) {
SQLSMALLINT len;
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
TSRMLS_CC );
if( !is_truncated_warning( state )) {
break;
}
}
SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" );
// if the type of the field returns the total to be read, we use that and preallocate the buffer
if( last_field_len != SQL_NO_TOTAL ) {
CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
already_read += to_read - already_read;
to_read = last_field_len;
buffer.resize( to_read + extra + sizeof( SQLULEN ));
output_buffer_len = reinterpret_cast<SQLLEN*>( buffer.get() );
// record the size of the field since we have it available
*output_buffer_len = last_field_len;
full_length_returned = true;
}
// otherwise allocate another chunk of memory to read in
else {
already_read += to_read - already_read;
to_read *= 2;
CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
buffer.resize( to_read + extra + sizeof( SQLULEN ));
output_buffer_len = reinterpret_cast<SQLLEN*>( buffer.get() );
}
} while( true );
SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" );
// most LOB field types return the total length in the last_field_len, but some field types such as XML
// only return the amount read on the last read
if( !full_length_returned ) {
*output_buffer_len = already_read + last_field_len;
}
char* return_buffer = buffer;
buffer.transferred();
return return_buffer;
}
}

View file

@ -1,2189 +0,0 @@
#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 3.2 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 HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef PHP_WIN32
#define PHP_SQLSRV_API __declspec(dllexport)
#else
#define PHP_SQLSRV_API
#endif
// OACR is an internal Microsoft static code analysis tool
#if defined(OACR)
#include <oacr.h>
OACR_WARNING_PUSH
OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." )
OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." )
OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." )
OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." )
OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." )
OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." )
#else
// define to eliminate static analysis hints in the code
#define OACR_WARNING_SUPPRESS( warning, msg )
#endif
extern "C" {
#pragma warning(push)
#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 )
#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
#include "php.h"
#include "php_globals.h"
#include "php_ini.h"
#include "ext/standard/php_standard.h"
#include "ext/standard/info.h"
#pragma warning(pop)
#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
#include <sql.h>
#include <sqlext.h>
#if !defined(WC_ERR_INVALID_CHARS)
// imported from winnls.h as it isn't included by 5.3.0
#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars
#endif
// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when
// we use std::copy, which causes compilation to fail since we compile with warnings as errors.
#if defined(ZEND_DEBUG) && defined(inline)
#undef inline
#endif
#include <deque>
#include <map>
#include <algorithm>
#include <limits>
#include <cassert>
#include <strsafe.h>
// included for SQL Server specific constants
#include "msodbcsql.h"
//*********************************************************************************************************************************
// Constants and Types
//*********************************************************************************************************************************
// constants for maximums in SQL Server
const int SS_MAXCOLNAMELEN = 128;
const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
const int SQL_SERVER_MAX_PRECISION = 38;
const int SQL_SERVER_MAX_TYPE_SIZE = 0;
const int SQL_SERVER_MAX_PARAMS = 2100;
// max size of a date time string when converting from a DateTime object to a string
const int MAX_DATETIME_STRING_LEN = 256;
// precision and scale for the date time types between servers
const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23;
const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3;
const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34;
const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7;
// types for conversions on output parameters (though they can be used for input parameters, they are ignored)
enum SQLSRV_PHPTYPE {
MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type
SQLSRV_PHPTYPE_NULL = 1,
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STREAM,
MAX_SQLSRV_PHPTYPE, // highest value for a php type
SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid
};
// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting
// information as a string or a stream.
enum SQLSRV_ENCODING {
SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables.
SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection
SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData
SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData
SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR,
SQLSRV_ENCODING_UTF8 = CP_UTF8,
};
// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object.
enum SQLSRV_FETCH_TYPE {
MIN_SQLSRV_FETCH = 1, // lowest value for fetch type
SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices
SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names
SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys
MAX_SQLSRV_FETCH = 3, // highest value for fetch type
};
// buffer size of a sql state (including the null character)
const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1;
// buffer size allocated to retrieve data from a PHP stream. This number
// was chosen since PHP doesn't return more than 8k at a time even if
// the amount requested was more.
const int PHP_STREAM_BUFFER_SIZE = 8192;
// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants.
// The size is the column size or precision, and scale is the decimal digits for precise numeric types.
union sqlsrv_sqltype {
struct typeinfo_t {
int type:9;
int size:14;
int scale:8;
} typeinfo;
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;
long value;
};
// static assert for enforcing compile time conditions
template <bool b>
struct sqlsrv_static_assert;
template <>
struct sqlsrv_static_assert<true> { static const int value = 1; };
#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() )
//*********************************************************************************************************************************
// Logging
//*********************************************************************************************************************************
// log_callback
// a driver specific callback for logging messages
// severity - severity of the message: notice, warning, or error
// msg - the message to log in a FormatMessage style formatting
// print_args - args to the message
typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args );
// each driver must register a log callback. This should be the first thing a driver does.
void core_sqlsrv_register_logger( log_callback );
// a simple wrapper around a PHP error logging function.
void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... );
// a macro to make it convenient to use the function.
#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ )
// mask for filtering which severities are written to the log
enum logging_severity {
SEV_ERROR = 0x01,
SEV_WARNING = 0x02,
SEV_NOTICE = 0x04,
SEV_ALL = -1,
};
// Kill the PHP process and log the message to PHP
void die( const char* msg, ... );
#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); }
//*********************************************************************************************************************************
// Resource/Memory Management
//*********************************************************************************************************************************
// the macro max is defined and overrides the call to max in the allocator class
#pragma push_macro( "max" )
#undef max
// new memory allocation/free debugging facilities to help us verify that all allocations are being
// released in a timely manner and not just at the end of the script.
// Zend has memory logging and checking, but it can generate a lot of noise for just one extension.
// It's meant for internal use but might be useful for people adding features to our extension.
// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases
// must be done with sqlsrv_malloc and sqlsrv_free.
// #define SQLSRV_MEM_DEBUG 1
#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG )
inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line )
{
void* ptr = emalloc( size );
LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr );
return ptr;
}
inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line )
{
OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" );
if(( element_count > 0 && element_size > 0 ) &&
( element_count > element_size * element_count || element_size > element_size * element_count )) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count > element_size * element_count + extra ) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count + extra == 0 ) {
DIE( "Allocation size must be more than 0" );
}
void* ptr = emalloc( element_size * element_count + extra );
LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr );
return ptr;
}
inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line )
{
void* ptr = erealloc( original, size );
LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original );
return ptr;
}
inline void sqlsrv_free_trace( void* ptr, const char* file, int line )
{
LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line );
efree( ptr );
}
#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ )
#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ )
#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ )
#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ )
#else
inline void* sqlsrv_malloc( size_t size )
{
return emalloc( size );
}
inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra )
{
OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" );
if(( element_count > 0 && element_size > 0 ) &&
( element_count > element_size * element_count || element_size > element_size * element_count )) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count > element_size * element_count + extra ) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count + extra == 0 ) {
DIE( "Allocation size must be more than 0" );
}
return emalloc( element_size * element_count + extra );
}
inline void* sqlsrv_realloc( void* buffer, size_t size )
{
return erealloc( buffer, size );
}
inline void sqlsrv_free( void* ptr )
{
efree( ptr );
}
#endif
// trait class that allows us to assign const types to an auto_ptr
template <typename T>
struct remove_const {
typedef T type;
};
template <typename T>
struct remove_const<const T*> {
typedef T* type;
};
// allocator that uses the zend memory manager to manage memory
// this allows us to use STL classes that still work with Zend objects
template<typename T>
struct sqlsrv_allocator {
// typedefs used by the STL classes
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// conversion typedef (used by list and other STL classes)
template<typename U>
struct rebind {
typedef sqlsrv_allocator<U> other;
};
inline sqlsrv_allocator() {}
inline ~sqlsrv_allocator() {}
inline sqlsrv_allocator( sqlsrv_allocator const& ) {}
template<typename U>
inline sqlsrv_allocator( sqlsrv_allocator<U> const& ) {}
// address (doesn't work if the class defines operator&)
inline pointer address( reference r )
{
return &r;
}
inline const_pointer address( const_reference r )
{
return &r;
}
// memory allocation/deallocation
inline pointer allocate( size_type cnt,
typename std::allocator<void>::const_pointer = 0 )
{
return reinterpret_cast<pointer>( sqlsrv_malloc(cnt, sizeof (T), 0));
}
inline void deallocate( pointer p, size_type )
{
sqlsrv_free(p);
}
// size
inline size_type max_size( void ) const
{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
// object construction/destruction
inline void construct( pointer p, const T& t )
{
new(p) T(t);
}
inline void destroy(pointer p)
{
p->~T();
}
// equality operators
inline bool operator==( sqlsrv_allocator const& )
{
return true;
}
inline bool operator!=( sqlsrv_allocator const& a )
{
return !operator==(a);
}
};
// base class for auto_ptrs that we define below. It provides common operators and functions
// used by all the classes.
template <typename T, typename Subclass>
class sqlsrv_auto_ptr {
public:
sqlsrv_auto_ptr( void ) : _ptr( NULL )
{
}
~sqlsrv_auto_ptr( void )
{
static_cast<Subclass*>(this)->reset( NULL );
}
// call when ownership is transferred
void transferred( void )
{
_ptr = NULL;
}
// explicit function to get the pointer.
T* get( void ) const
{
return _ptr;
}
// cast operator to allow auto_ptr to be used where a normal const * can be.
operator const T* () const
{
return _ptr;
}
// cast operator to allow auto_ptr to be used where a normal pointer can be.
operator typename remove_const<T*>::type () const
{
return _ptr;
}
operator bool() const
{
return _ptr != NULL;
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( int index ) const
{
return _ptr[ index ];
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( unsigned int index ) const
{
return _ptr[ index ];
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( long index ) const
{
return _ptr[ index ];
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( unsigned short index ) const
{
return _ptr[ index ];
}
// access elements of a structure through the auto ptr
T* const operator->( void ) const
{
return _ptr;
}
// value from reference operator (i.e., i = *(&i); or *i = blah;)
T& operator*() const
{
return *_ptr;
}
// allow the use of the address-of operator to simulate a **.
// Note: this operator conflicts with storing these within an STL container. If you need
// to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp()
T** operator&( void )
{
return &_ptr;
}
protected:
sqlsrv_auto_ptr( T* ptr ) :
_ptr( ptr )
{
}
sqlsrv_auto_ptr( sqlsrv_auto_ptr& src )
{
if( _ptr ) {
static_cast<Subclass*>(this)->reset( src._ptr );
}
src.transferred();
}
// assign a new pointer to the auto_ptr. It will free the previous memory block
// because ownership is deemed finished.
T* operator=( T* ptr )
{
static_cast<Subclass*>( this )->reset( ptr );
return ptr;
}
T* _ptr;
};
// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer
// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is
// freed until the variable is destroyed (out of scope) or ownership is transferred using the function
// "transferred".
// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function.
template <typename T>
class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> > {
public:
sqlsrv_malloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( NULL )
{
}
sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( T* ptr = NULL )
{
if( _ptr )
sqlsrv_free( (void*) _ptr );
_ptr = ptr;
}
T* operator=( T* ptr )
{
return sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >::operator=( ptr );
}
void operator=( sqlsrv_malloc_auto_ptr<T>& src )
{
T* p = src.get();
src.transferred();
this->_ptr = p;
}
// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function.
// has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer
void resize( size_t new_size )
{
_ptr = reinterpret_cast<T*>( sqlsrv_realloc( _ptr, new_size ));
}
};
// auto ptr for Zend hash tables. Used to clean up a hash table allocated when
// something caused an early exit from the function. This is used when the hash_table is
// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr.
class hash_auto_ptr : public sqlsrv_auto_ptr<HashTable, hash_auto_ptr> {
public:
hash_auto_ptr( void ) :
sqlsrv_auto_ptr<HashTable, hash_auto_ptr>( NULL )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( HashTable* ptr = NULL )
{
if( _ptr ) {
zend_hash_destroy( _ptr );
FREE_HASHTABLE( _ptr );
}
_ptr = ptr;
}
HashTable* operator=( HashTable* ptr )
{
return sqlsrv_auto_ptr<HashTable, hash_auto_ptr>::operator=( ptr );
}
private:
hash_auto_ptr( HashTable const& hash );
hash_auto_ptr( hash_auto_ptr const& hash );
};
// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr.
// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed
// (out of scope) or ownership is transferred using the function "transferred".
class zval_auto_ptr : public sqlsrv_auto_ptr<zval, zval_auto_ptr> {
public:
zval_auto_ptr( void )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( zval* ptr = NULL )
{
if( _ptr )
zval_ptr_dtor( &_ptr );
_ptr = ptr;
}
zval* operator=( zval* ptr )
{
return sqlsrv_auto_ptr<zval, zval_auto_ptr>::operator=( ptr );
}
#if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3)
operator zval_gc_info*( void )
{
return reinterpret_cast<zval_gc_info*>(_ptr);
}
#endif
private:
zval_auto_ptr( const zval_auto_ptr& src );
};
#pragma pop_macro( "max" )
//*********************************************************************************************************************************
// sqlsrv_error
//*********************************************************************************************************************************
// *** PHP specific errors ***
// sqlsrv errors are held in a structure of this type used by the driver handle_error functions
// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage
// into the error message before returning it.
// base class which can be instatiated with aggregates (see error constants)
struct sqlsrv_error_const {
SQLCHAR* sqlstate;
SQLCHAR* native_message;
SQLINTEGER native_code;
bool format;
};
// subclass which is used by the core layer to instantiate ODBC errors
struct sqlsrv_error : public sqlsrv_error_const {
sqlsrv_error( void )
{
sqlstate = NULL;
native_message = NULL;
native_code = -1;
format = false;
}
sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false )
{
sqlstate = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 ));
strcpy_s( reinterpret_cast<char*>( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast<const char*>( sql_state ));
strcpy_s( reinterpret_cast<char*>( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>( message ));
native_code = code;
format = printf_format;
}
sqlsrv_error( sqlsrv_error_const const& prototype )
{
sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format );
}
~sqlsrv_error( void )
{
if( sqlstate != NULL ) {
sqlsrv_free( sqlstate );
}
if( native_message != NULL ) {
sqlsrv_free( native_message );
}
}
};
// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete
class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr > {
public:
sqlsrv_error_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >( NULL )
{
}
sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >( (sqlsrv_error_auto_ptr&) src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( sqlsrv_error* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_error();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_error* operator=( sqlsrv_error* ptr )
{
return sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >::operator=( ptr );
}
// unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much
// sense. Only the last one would have anything in it.
void operator=( sqlsrv_error_auto_ptr& src )
{
sqlsrv_error* p = src.get();
src.transferred();
this->_ptr = p;
}
};
//*********************************************************************************************************************************
// Context
//*********************************************************************************************************************************
class sqlsrv_context;
struct sqlsrv_conn;
// error_callback
// a driver specific callback for processing errors.
// ctx - the context holding the handles
// sqlsrv_error_code - specific error code to return.
typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args );
// sqlsrv_context
// a context holds relevant information to be passed with a connection and statement objects.
class sqlsrv_context {
public:
sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) :
handle_( SQL_NULL_HANDLE ),
handle_type_( type ),
err_( e ),
name_( NULL ),
driver_( drv ),
last_error_(),
encoding_( encoding )
{
}
sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) :
handle_( h ),
handle_type_( t ),
err_( e ),
name_( NULL ),
driver_( drv ),
last_error_(),
encoding_( encoding )
{
}
sqlsrv_context( sqlsrv_context const& ctx ) :
handle_( ctx.handle_ ),
handle_type_( ctx.handle_type_ ),
err_( ctx.err_ ),
name_( ctx.name_ ),
driver_( ctx.driver_ ),
last_error_( ctx.last_error_ )
{
}
void set_func( const char* f )
{
name_ = f;
}
void set_last_error( sqlsrv_error_auto_ptr& last_error )
{
last_error_ = last_error;
}
sqlsrv_error_auto_ptr& last_error( void )
{
return last_error_;
}
// since the primary responsibility of a context is to hold an ODBC handle, we
// provide these convenience operators for using them interchangeably
operator SQLHANDLE ( void ) const
{
return handle_;
}
error_callback error_handler( void ) const
{
return err_;
}
SQLHANDLE handle( void ) const
{
return handle_;
}
SQLSMALLINT handle_type( void ) const
{
return handle_type_;
}
const char* func( void ) const
{
return name_;
}
void* driver( void ) const
{
return driver_;
}
void set_driver( void* driver )
{
this->driver_ = driver;
}
void invalidate( void )
{
if( handle_ != SQL_NULL_HANDLE ) {
::SQLFreeHandle( handle_type_, handle_ );
}
handle_ = SQL_NULL_HANDLE;
}
bool valid( void )
{
return handle_ != SQL_NULL_HANDLE;
}
SQLSRV_ENCODING encoding( void ) const
{
return encoding_;
}
void set_encoding( SQLSRV_ENCODING e )
{
encoding_ = e;
}
private:
SQLHANDLE handle_; // ODBC handle for this context
SQLSMALLINT handle_type_; // type of the ODBC handle
const char* name_; // function name currently executing this context
error_callback err_; // driver error callback if error occurs in core layer
void* driver_; // points back to the driver for PDO
sqlsrv_error_auto_ptr last_error_; // last error that happened on this object
SQLSRV_ENCODING encoding_; // encoding of the context
};
const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista
// maps an IANA encoding to a code page
struct sqlsrv_encoding {
const char* iana;
unsigned int iana_len;
unsigned int code_page;
bool not_for_connection;
sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ):
iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn )
{
}
};
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// variables set during initialization
extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in
extern HashTable* g_encodings; // encodings supported by this driver
void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC );
void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp );
// environment context used by sqlsrv_connect for when a connection error occurs.
struct sqlsrv_henv {
sqlsrv_context ctx;
sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) :
ctx( handle, SQL_HANDLE_ENV, e, drv )
{
}
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
// supported server versions (determined at connection time)
enum SERVER_VERSION {
SERVER_VERSION_UNKNOWN = -1,
SERVER_VERSION_2000 = 8,
SERVER_VERSION_2005,
SERVER_VERSION_2008, // use this for anything 2008 or later
};
// forward decl
struct sqlsrv_stmt;
struct stmt_option;
// *** connection resource structure ***
// this is the resource structure returned when a connection is made.
struct sqlsrv_conn : public sqlsrv_context {
// instance variables
SERVER_VERSION server_version; // version of the server that we're connected to
// initialize with default values
sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
}
// sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be
// called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating
// the sqlsrv_conn with a connection.
};
enum SQLSRV_STMT_OPTIONS {
SQLSRV_STMT_OPTION_INVALID,
SQLSRV_STMT_OPTION_QUERY_TIMEOUT,
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
SQLSRV_STMT_OPTION_SCROLLABLE,
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
// Driver specific connection options
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
};
namespace ODBCConnOptions {
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char LoginTimeout[] = "LoginTimggeout";
const char MARS_ODBC[] = "MARS_Connection";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
const char QuotedId[] = "QuotedId";
const char TraceFile[] = "TraceFile";
const char TraceOn[] = "TraceOn";
const char TrustServerCertificate[] = "TrustServerCertificate";
const char TransactionIsolation[] = "TransactionIsolation";
const char WSID[] = "WSID";
const char UID[] = "UID";
const char PWD[] = "PWD";
const char SERVER[] = "Server";
}
enum SQLSRV_CONN_OPTIONS {
SQLSRV_CONN_OPTION_INVALID,
SQLSRV_CONN_OPTION_APP,
SQLSRV_CONN_OPTION_CHARACTERSET,
SQLSRV_CONN_OPTION_CONN_POOLING,
SQLSRV_CONN_OPTION_DATABASE,
SQLSRV_CONN_OPTION_ENCRYPT,
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
SQLSRV_CONN_OPTION_MARS,
SQLSRV_CONN_OPTION_QUOTED_ID,
SQLSRV_CONN_OPTION_TRACE_FILE,
SQLSRV_CONN_OPTION_TRACE_ON,
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
SQLSRV_CONN_OPTION_WSID,
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
// Driver specific connection options
SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000,
};
#define NO_ATTRIBUTE -1
// type of connection attributes
enum CONN_ATTR_TYPE {
CONN_ATTR_INT,
CONN_ATTR_BOOL,
CONN_ATTR_STRING,
CONN_ATTR_INVALID,
};
// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or
// sets an attribute)
struct connection_option {
// the name of the option as passed in by the user
const char * sqlsrv_name;
unsigned int sqlsrv_len;
unsigned int conn_option_key;
// the name of the option in the ODBC connection string
const char * odbc_name;
unsigned int odbc_len;
enum CONN_ATTR_TYPE value_type;
// process the connection type
// return whether or not the function was successful in processing the connection option
void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC );
};
// connection attribute functions
template <unsigned int Attr>
struct str_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )),
Z_STRLEN_P( value ) TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
// simply add the parsed value to the connection string
struct conn_str_append_func {
static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC );
};
struct conn_null_func {
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC );
// *** connection functions ***
sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
const char* server, const char* uid, const char* pwd,
HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[],
void* driver, const char* driver_func TSRMLS_DC );
void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC );
void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval* server_info TSRMLS_DC );
void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC );
void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC );
bool core_is_conn_opt_value_escaped( const char* value, int value_len );
int core_str_zval_is_true( zval* str_zval );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_functor {
virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC );
};
struct stmt_option_query_timeout : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
struct stmt_option_send_at_exec : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
struct stmt_option_buffered_query_limit : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
// used to hold the table for statment options
struct stmt_option {
const char * name; // name of the statement option
unsigned int name_len; // name length
unsigned int key;
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;
int stmt_index;
sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) :
stream_z( str_z ), encoding( enc )
{
}
sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL )
{
}
};
// close any active stream
void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC );
extern php_stream_wrapper g_sqlsrv_stream_wrapper;
// resource constants used when registering the stream type with PHP
#define SQLSRV_STREAM_WRAPPER "sqlsrv"
#define SQLSRV_STREAM "sqlsrv_stream"
// holds the output parameter information. Strings also need the encoding and other information for
// after processing. Only integer, float, and strings are allowable output parameters.
struct sqlsrv_output_param {
zval* param_z;
SQLSRV_ENCODING encoding;
int param_num; // used to index into the ind_or_len of the statement
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
bool is_bool;
// string output param constructor
sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) :
param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false )
{
}
// every other type output parameter constructor
sqlsrv_output_param( zval* p_z, int num, bool is_bool ) :
param_z( p_z ),
param_num( num ),
encoding( SQLSRV_ENCODING_INVALID ),
original_buffer_len( -1 ),
is_bool( is_bool )
{
}
};
// forward decls
struct sqlsrv_result_set;
// *** Statement resource structure ***
struct sqlsrv_stmt : public sqlsrv_context {
void free_param_data( TSRMLS_D );
virtual void new_result_set( TSRMLS_D );
sqlsrv_conn* conn; // Connection that created this statement
bool executed; // Whether the statement has been executed yet (used for error messages)
bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row
sqlsrv_result_set* current_results; // Current result set
SQLULEN cursor_type; // Type of cursor for the current result set
bool has_rows; // Has_rows is set if there are actual rows in the row set
bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called
int last_field_index; // last field retrieved by core_sqlsrv_get_field
bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the
// last results
unsigned long query_timeout; // maximum allowed statement execution time
unsigned long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
// holds output pointers for SQLBindParameter
// We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving
// memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold
std::deque<SQLLEN> param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter
zval* param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP
zval* output_params; // hold all the output parameters
zval* param_streams; // track which streams to send data to the server
zval* param_datetime_buffers; // datetime strings to be converted back to DateTime objects
bool send_streams_at_exec; // send all stream data right after execution before returning
sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter
unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string
// to the server)
zval* field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals
zval* active_stream; // the currently active stream reading data from the database
sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC );
virtual ~sqlsrv_stmt( void );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0;
};
// *** field metadata struct ***
struct field_meta_data {
sqlsrv_malloc_auto_ptr<SQLCHAR> field_name;
SQLSMALLINT field_name_len;
SQLSMALLINT field_type;
SQLULEN field_size;
SQLULEN field_precision;
SQLSMALLINT field_scale;
SQLSMALLINT field_is_nullable;
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
field_scale (0), field_is_nullable(0)
{
}
~field_meta_data()
{
}
};
// *** statement constants ***
// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value
const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff;
const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int)
// uninitialized query timeout value
const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff;
// special buffered query constant
const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant
// factory to create a statement
typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC );
// *** statement functions ***
sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht,
const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC );
void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z,
SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size,
SQLSMALLINT decimal_digits TSRMLS_DC );
void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 );
field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC );
bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string,
__out void** field_value, __out SQLLEN* field_length, bool cache_field,
__out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC );
bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true );
void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z TSRMLS_DC );
void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC );
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC );
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC );
//*********************************************************************************************************************************
// Result Set
//*********************************************************************************************************************************
// Abstract the result set so that a result set can either be used as is from ODBC or buffered.
// This is not a complete abstraction of a result set. Only enough is abstracted to allow for
// information and capabilities normally not available when a result set is not buffered
// (e.g., forward only vs buffered means row count is available and cursor movement is possible).
// Otherwise, normal ODBC calls are still valid and should be used to get information about the
// result set (e.g., SQLNumResultCols).
struct sqlsrv_result_set {
sqlsrv_stmt* odbc;
explicit sqlsrv_result_set( sqlsrv_stmt* );
virtual ~sqlsrv_result_set( void ) { }
virtual bool cached( int field_index ) = 0;
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0;
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )= 0;
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0;
virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0;
virtual SQLLEN row_count( TSRMLS_D ) = 0;
};
struct sqlsrv_odbc_result_set : public sqlsrv_result_set {
explicit sqlsrv_odbc_result_set( sqlsrv_stmt* );
virtual ~sqlsrv_odbc_result_set( void );
virtual bool cached( int field_index ) { return false; }
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC );
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC );
virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number );
virtual SQLLEN row_count( TSRMLS_D );
private:
// prevent invalid instantiations and assignments
sqlsrv_odbc_result_set( void );
sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& );
sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& );
};
struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
struct meta_data {
SQLSMALLINT type;
SQLSMALLINT c_type; // convenience
SQLULEN offset; // in bytes
SQLULEN length; // in bytes
SQLSMALLINT scale;
static const SQLULEN SIZE_UNKNOWN = 0;
};
// default maximum amount of memory that a buffered query can consume
#define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings
static const unsigned long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB
static const long BUFFERED_QUERY_LIMIT_INVALID = 0;
explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC );
virtual ~sqlsrv_buffered_result_set( void );
virtual bool cached( int field_index ) { return true; }
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC );
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC );
virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number );
virtual SQLLEN row_count( TSRMLS_D );
// buffered result set specific
SQLSMALLINT column_count( void )
{
return col_count;
}
struct meta_data& col_meta_data( SQLSMALLINT i )
{
return meta[i];
}
private:
// prevent invalid instantiations and assignments
sqlsrv_buffered_result_set( void );
sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& );
sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& );
HashTable* cache; // rows of data kept in index based hash table
SQLSMALLINT col_count; // number of columns in the current result set
meta_data* meta; // metadata for fields in the cache
SQLLEN current; // 1 based, 0 means before first row
sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here
SQLUSMALLINT last_field_index; // the last field data retrieved from
SQLLEN read_so_far; // position within string to read from (for partial reads of strings)
sqlsrv_malloc_auto_ptr<SQLCHAR> temp_string; // temp buffer to hold a converted field while in use
SQLLEN temp_length; // number of bytes in the temp conversion buffer
typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t;
// two dimentional sparse matrix that holds the [from][to] functions that do conversions
static conv_matrix_t conv_matrix;
// string conversion functions
SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// long conversion functions
SQLRETURN to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length );
SQLRETURN long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// double conversion functions
SQLRETURN to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length );
SQLRETURN double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// string to number conversion functions
// Future: See if these can be converted directly to template member functions
SQLRETURN string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// utility functions for conversions
unsigned char* get_row( void );
};
//*********************************************************************************************************************************
// Utility
//*********************************************************************************************************************************
// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler.
// We use this since the unused variables are buried in the PHP_FUNCTION macro.
#define SQLSRV_UNUSED( var ) var;
// do a heap check in debug mode, but only print errors, not all of the allocations
#define MEMCHECK_SILENT 1
// utility functions shared by multiple callers across files
bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len);
bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen );
wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string,
unsigned int mbcs_len, __out unsigned int* utf16_len );
//*********************************************************************************************************************************
// Error handling routines and Predefined Errors
//*********************************************************************************************************************************
enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_ODBC,
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_ZEND_HASH,
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
SQLSRV_ERROR_ZEND_STREAM,
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
SQLSRV_ERROR_FETCH_PAST_END,
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
SQLSRV_ERROR_NO_FIELDS,
SQLSRV_ERROR_INVALID_TYPE,
SQLSRV_ERROR_FETCH_NOT_CALLED,
SQLSRV_ERROR_NO_DATA,
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED,
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
SQLSRV_ERROR_INVALID_CONNECTION_KEY,
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
SQLSRV_ERROR_INVALID_OPTION_KEY,
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
SQLSRV_ERROR_DATETIME_CONVERSION_FAILED,
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
SQLSRV_ERROR_STREAM_CREATE,
SQLSRV_ERROR_MARS_OFF,
SQLSRV_ERROR_FIELD_INDEX_ERROR,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
};
// the message returned by ODBC Driver 11 for SQL Server
const char CONNECTION_BUSY_ODBC_ERROR[] = "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for "
"another command";
// SQLSTATE for all internal errors
extern SQLCHAR IMSSP[];
// SQLSTATE for all internal warnings
extern SQLCHAR SSPWARN[];
// flags passed to sqlsrv_errors to filter its return values
enum error_handling_flags {
SQLSRV_ERR_ERRORS,
SQLSRV_ERR_WARNINGS,
SQLSRV_ERR_ALL
};
// *** internal error macros and functions ***
// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the
// errno is 1 based. It returns it as an array with 3 members:
// 1/SQLSTATE) sqlstate
// 2/code) driver specific error code
// 3/message) driver specific error message
// The fetch type determines if the indices are numeric, associative, or both.
bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, __out sqlsrv_error_auto_ptr& error,
logging_severity severity TSRMLS_DC );
// format and return a driver specfic error
void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error,
sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args );
// return the message for the HRESULT returned by GetLastError. Some driver errors use this to
// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails.
const char* get_last_error_message( DWORD last_error = 0 );
// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer
DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... );
// convenience functions that overload either a reference or a pointer so we can use
// either in the CHECK_* functions.
inline bool call_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... )
{
va_list print_params;
va_start( print_params, warning );
bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params );
va_end( print_params );
return ignored;
}
inline bool call_error_handler( sqlsrv_context* ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... )
{
va_list print_params;
va_start( print_params, warning );
bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params );
va_end( print_params );
return ignored;
}
// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which
// we don't want on a web server
#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( SQLCHAR* state )
{
#if defined(ZEND_DEBUG)
if( state == NULL || strlen( reinterpret_cast<char*>( state )) != 5 ) { \
DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \
}
#endif
return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4');
}
// Macros for handling errors. These macros are simplified if statements that take boilerplate
// code down to a single line to avoid distractions in the code.
#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \
bool flag##unique = (condition); \
bool ignored##unique = true; \
if (flag##unique) { \
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, NULL, __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, NULL 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( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC )
{
// We check for the 'connection busy' error caused by having MultipleActiveResultSets off
// and return a more helpful message prepended to the ODBC errors if that error occurs
if( !SQL_SUCCEEDED( r )) {
SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLSMALLINT len = 0;
SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT,
err_msg, SQL_MAX_MESSAGE_LENGTH, &len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
if(( len == sizeof( CONNECTION_BUSY_ODBC_ERROR ) - 1 ) &&
!strcmp( reinterpret_cast<const char*>( err_msg ), CONNECTION_BUSY_ODBC_ERROR )) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF );
}
}
}
// *** ODBC wrappers ***
// wrap the ODBC functions to throw exceptions rather than use the return value to signal errors
// some of the signatures have been altered to be more convenient since the return value is no longer
// required to return the status of the call (e.g., SQLNumResultCols).
// These functions take the sqlsrv_context type. However, since the error handling code can alter
// the context to hold the error, they are not passed as const.
inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC )
{
SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier,
diag_info_buffer, buffer_length, out_buffer_length );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
return r;
}
inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle,
__out_ecount(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr );
CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) {
throw CoreException();
}
}
inline void SQLBindParameter( sqlsrv_stmt* stmt,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
__inout SQLPOINTER ParameterValuePtr,
SQLLEN BufferLength,
__inout SQLLEN * StrLen_Or_IndPtr
TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize,
DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier,
__out SQLPOINTER field_type_char, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length, __out SQLLEN* field_type_num TSRMLS_DC )
{
SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char,
buffer_length, out_buffer_length, field_type_num );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, __out_z SQLCHAR* col_name, SQLSMALLINT col_name_length,
__out SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, __out SQLULEN* col_size,
__out SQLSMALLINT* decimal_digits, __out SQLSMALLINT* nullable TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out,
data_type, col_size, decimal_digits, nullable);
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC )
{
SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
// SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success
inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC )
{
SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast<SQLCHAR*>( sql ), SQL_NTS );
check_for_mars_error( stmt, r TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast<SQLWCHAR*>( wsql ), SQL_NTS );
check_for_mars_error( stmt, r TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
// SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success
inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLExecute( stmt->handle() );
check_for_mars_error( stmt, r TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC )
{
SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
// wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine
inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {}
}
inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )
{
SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length );
if( r == SQL_NO_DATA )
return r;
CHECK_SQL_ERROR( r, stmt ) {
throw CoreException();
}
if( handle_warning ) {
CHECK_SQL_WARNING_AS_ERROR( r, stmt ) {
throw CoreException();
}
}
return r;
}
inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, __out SQLPOINTER info_value, SQLSMALLINT buffer_len,
__out SQLSMALLINT* str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLGetTypeInfo( stmt->handle(), data_type );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
// SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set.
inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r = ::SQLMoreResults( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
SQLSMALLINT num_cols;
r = ::SQLNumResultCols( stmt->handle(), &num_cols );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return num_cols;
}
// SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more
// parameters or when the parameters are all processed.
inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, __out SQLPOINTER* value_ptr_ptr TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLParamData( stmt->handle(), value_ptr_ptr );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLPrepareW( stmt->handle(), sql, sql_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
SQLLEN rows_affected;
r = ::SQLRowCount( stmt->handle(), &rows_affected );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return rows_affected;
}
inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
}
inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
}
inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC )
{
SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
// *** zend wrappers ***
// exception thrown when a zend function wrapped here fails.
// wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw
// exceptions when an error occurs. They are prefaced with sqlsrv_<zend_function_name> because many of the zend functions are
// actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system.
// If there is a zend function in the source that isn't found here, it is because it returns void and there is no error
// that can be thrown from it.
inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, unsigned int index, zval* value TSRMLS_DC)
{
int zr = ::add_index_zval( array, index, value );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC)
{
int zr = ::add_next_index_zval( array, value );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, char* key TSRMLS_DC )
{
int zr = ::add_assoc_null( array_z, key );
CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, char* key, long val TSRMLS_DC )
{
int zr = ::add_assoc_long( array_z, key, val );
CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, char* key, char* val, bool duplicate TSRMLS_DC )
{
int zr = ::add_assoc_string( array_z, key, val, duplicate );
CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_array_init( sqlsrv_context& ctx, __out zval* new_array TSRMLS_DC)
{
int zr = ::array_init( new_array );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval** stream_z TSRMLS_DC )
{
// this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment
php_stream_from_zval_no_verify( stream, stream_z );
CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_get_current_data( sqlsrv_context& ctx, HashTable* ht, __out void** output_data TSRMLS_DC )
{
int zr = ::zend_hash_get_current_data( ht, output_data );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, int index TSRMLS_DC )
{
int zr = ::zend_hash_index_del( ht, index );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, unsigned long index, void* data,
uint data_size TSRMLS_DC )
{
int zr = ::zend_hash_index_update( ht, index, data, data_size, NULL );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, void* data,
uint data_size TSRMLS_DC )
{
int zr = ::zend_hash_next_index_insert( ht, data, data_size, NULL );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_init( sqlsrv_context& ctx, HashTable* ht, unsigned int initial_size, hash_func_t hash_fn,
dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC )
{
int zr = ::zend_hash_init( ht, initial_size, hash_fn, dtor_fn, persistent );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, char* key, unsigned int key_len, void** data,
unsigned int data_size, void **pDest TSRMLS_DC )
{
int zr = ::zend_hash_add( ht, key, key_len, data, data_size, pDest );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
template <typename Statement>
sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC )
{
return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC );
}
template <typename Connection>
sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC )
{
return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC );
}
} // namespace core
#endif // CORE_SQLSRV_H

View file

@ -1,2436 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stmt.cpp
//
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
namespace {
// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the
// results of every field request, and if it is out of order, we cache those for preceding fields.
struct field_cache {
void* value;
SQLLEN len;
sqlsrv_phptype type;
field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t )
: type( t )
{
// if the value is NULL, then just record a NULL pointer
if( field_value != NULL ) {
value = sqlsrv_malloc( field_len );
memcpy( value, field_value, field_len );
len = field_len;
}
else {
value = NULL;
len = 0;
}
}
// no destructor because we don't want to release the memory when it goes out of scope, but instead we
// rely on the hash table destructor to free the memory
};
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads
const unsigned int UTF8_MIDBYTE_MASK = 0xc0;
const unsigned int UTF8_MIDBYTE_TAG = 0x80;
const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0;
const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0;
const unsigned int UTF8_3BYTESEQ_TAG = 0xe0;
const unsigned int UTF8_4BYTESEQ_TAG = 0xf0;
const unsigned int UTF8_NBYTESEQ_MASK = 0xf0;
// constants used to convert from a DateTime object to a string which is sent to the server.
// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx
namespace DateTime {
const char DATETIME_CLASS_NAME[] = "DateTime";
const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1;
const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P";
const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT );
const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u";
const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT );
const char DATE_FORMAT[] = "Y-m-d";
const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT );
}
// *** internal functions ***
// Only declarations are put here. Functions contain the documentation they need at their definition sites.
void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC );
size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC );
bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC );
bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z );
void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype
sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC );
// returns the ODBC C type constant that matches the PHP type and encoding given
SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC );
void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC );
// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate)
void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLSMALLINT& sql_type TSRMLS_DC );
void field_cache_dtor( void* data );
void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC );
void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type,
__out void** field_value, __out SQLLEN* field_len TSRMLS_DC );
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC );
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type );
// assure there is enough space for the output parameter string
void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding,
SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer,
SQLLEN& buffer_len TSRMLS_DC );
void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC );
// send all the stream data
void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC );
// called when a bound output string parameter is to be destroyed
void sqlsrv_output_param_dtor( void* data );
// called when a bound stream parameter is to be destroyed.
void sqlsrv_stream_dtor( void* data );
bool is_streamable_type( SQLINTEGER sql_type );
}
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) :
sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ),
conn( c ),
executed( false ),
past_fetch_end( false ),
current_results( NULL ),
cursor_type( SQL_CURSOR_FORWARD_ONLY ),
has_rows( false ),
fetch_called( false ),
last_field_index( -1 ),
past_next_result_end( false ),
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
send_streams_at_exec( true ),
current_stream( NULL, SQLSRV_ENCODING_DEFAULT ),
current_stream_read( 0 ),
query_timeout( QUERY_TIMEOUT_INVALID ),
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
active_stream( NULL )
{
// initialize the input string parameters array (which holds zvals)
MAKE_STD_ZVAL( param_input_strings );
core::sqlsrv_array_init( *conn, param_input_strings TSRMLS_CC );
// initialize the (input only) stream parameters (which holds sqlsrv_stream structures)
MAKE_STD_ZVAL( param_streams );
Z_TYPE_P( param_streams ) = IS_ARRAY;
ALLOC_HASHTABLE( Z_ARRVAL_P( param_streams ));
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( param_streams ), 5 /* # of buckets */, NULL /*hashfn*/,
sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC );
// initialize the (input only) datetime parameters of converted date time objects to strings
MAKE_STD_ZVAL( param_datetime_buffers );
array_init( param_datetime_buffers );
// initialize the output string parameters (which holds sqlsrv_output_param structures)
MAKE_STD_ZVAL( output_params );
Z_TYPE_P( output_params ) = IS_ARRAY;
ALLOC_HASHTABLE( Z_ARRVAL_P( output_params ));
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( output_params ), 5 /* # of buckets */, NULL /*hashfn*/,
sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC );
// initialize the field cache
MAKE_STD_ZVAL( field_cache );
Z_TYPE_P( field_cache ) = IS_ARRAY;
ALLOC_HASHTABLE( Z_ARRVAL_P( field_cache ));
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( field_cache ), 5 /* # of buckets */, NULL /*hashfn*/,
field_cache_dtor, 0 /*persistent*/ TSRMLS_CC );
}
// desctructor for sqlsrv statement.
sqlsrv_stmt::~sqlsrv_stmt( void )
{
if( active_stream ) {
TSRMLS_FETCH();
close_active_stream( this TSRMLS_CC );
}
// delete any current results
if( current_results ) {
current_results->~sqlsrv_result_set();
efree( current_results );
current_results = NULL;
}
invalidate();
zval_ptr_dtor( &param_input_strings );
zval_ptr_dtor( &output_params );
zval_ptr_dtor( &param_streams );
zval_ptr_dtor( &param_datetime_buffers );
zval_ptr_dtor( &field_cache );
}
// centralized place to release (without destroying the hash tables
// themselves) all the parameter data that accrues during the
// execution phase.
void sqlsrv_stmt::free_param_data( TSRMLS_D )
{
SQLSRV_ASSERT( Z_TYPE_P( param_input_strings ) == IS_ARRAY && Z_TYPE_P( param_streams ) == IS_ARRAY,
"sqlsrv_stmt::free_param_data: Param zvals aren't arrays." );
zend_hash_clean( Z_ARRVAL_P( param_input_strings ));
zend_hash_clean( Z_ARRVAL_P( output_params ));
zend_hash_clean( Z_ARRVAL_P( param_streams ));
zend_hash_clean( Z_ARRVAL_P( param_datetime_buffers ));
zend_hash_clean( Z_ARRVAL_P( field_cache ));
}
// to be called whenever a new result set is created, such as after an
// execute or next_result. Resets the state variables.
void sqlsrv_stmt::new_result_set( TSRMLS_D )
{
this->fetch_called = false;
this->has_rows = false;
this->past_next_result_end = false;
this->past_fetch_end = false;
this->last_field_index = -1;
// delete any current results
if( current_results ) {
current_results->~sqlsrv_result_set();
efree( current_results );
current_results = NULL;
}
// create a new result set
if( cursor_type == SQLSRV_CURSOR_BUFFERED ) {
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC );
}
else {
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this );
}
}
// core_sqlsrv_create_stmt
// Common code to allocate a statement from either driver. Returns a valid driver statement object or
// throws an exception if an error occurs.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// stmt_factory - factory method to create a statement.
// options_ht - A HashTable of user provided options to be set on the statement.
// valid_stmt_opts - An array of valid driver supported statement options.
// err - callback for error handling
// driver - reference to caller
// Return
// Returns the created statement
sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht,
const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC )
{
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
SQLHANDLE stmt_h = SQL_NULL_HANDLE;
try {
core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC );
stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC );
stmt->conn = conn;
// handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free
// in the catch block below.
stmt_h = SQL_NULL_HANDLE;
// process the options array given to core_sqlsrv_prepare.
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
for( zend_hash_internal_pointer_reset( options_ht );
zend_hash_has_more_elements( options_ht ) == SUCCESS;
zend_hash_move_forward( options_ht )) {
char *key = NULL;
unsigned int key_len = 0;
unsigned long index = -1;
zval** value_z = NULL;
int type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &index, 0, NULL );
// The driver layer should ensure a valid key.
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." );
core::sqlsrv_zend_hash_get_current_data( *(stmt->conn), options_ht, (void**) &value_z TSRMLS_CC );
const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC );
// if the key didn't match, then return the error to the script.
// The driver layer should ensure that the key is valid.
DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." );
// perform the actions the statement option needs done.
(*stmt_opt->func)( stmt, stmt_opt, *value_z TSRMLS_CC );
}
zend_hash_internal_pointer_end( options_ht );
}
sqlsrv_stmt* return_stmt = stmt;
stmt.transferred();
return return_stmt;
}
catch( core::CoreException& )
{
if( stmt ) {
conn->set_last_error( stmt->last_error() );
stmt->~sqlsrv_stmt();
}
// if allocating the handle failed before the statement was allocated, free the handle
if( stmt_h != SQL_NULL_HANDLE) {
::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h );
}
throw;
}
catch( ... ) {
DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." );
}
}
// core_sqlsrv_bind_param
// Binds a parameter using SQLBindParameter. It allocates memory and handles other details
// in translating between the driver and ODBC.
// Parameters:
// param_num - number of the parameter, 0 based
// param_z - zval of the parameter
// php_out_type - type to return for output parameter
// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults)
// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults)
// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale
// Return:
// Nothing, though an exception is thrown if an error occurs
// The php type of the parameter is taken from the zval.
// The sql type is given as a hint if the driver provides it.
void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z,
SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size,
SQLSMALLINT decimal_digits TSRMLS_DC )
{
SQLSMALLINT c_type;
SQLPOINTER buffer = NULL;
SQLLEN buffer_len = 0;
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT,
"core_sqlsrv_bind_param: Invalid parameter direction." );
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID,
"core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." );
try {
// check is only < because params are 0 based
CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) {
throw core::CoreException();
}
// resize the statements array of int_ptrs if the parameter isn't already set.
if( stmt->param_ind_ptrs.size() < param_num + 1 ) {
stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA );
}
SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ];
bool zval_was_null = (Z_TYPE_P( param_z ) == IS_NULL);
bool zval_was_bool = (Z_TYPE_P( param_z ) == IS_BOOL);
// if the user asks for for a specific type for input and output, make sure the data type we send matches the data we
// type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so
// we always let that match if they want a string back.
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
bool match = false;
switch( php_out_type ) {
case SQLSRV_PHPTYPE_INT:
if( zval_was_null || zval_was_bool ) {
convert_to_long( param_z );
}
match = Z_TYPE_P( param_z ) == IS_LONG;
break;
case SQLSRV_PHPTYPE_FLOAT:
if( zval_was_null ) {
convert_to_double( param_z );
}
match = Z_TYPE_P( param_z ) == IS_DOUBLE;
break;
case SQLSRV_PHPTYPE_STRING:
// anything can be converted to a string
convert_to_string( param_z );
match = true;
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
SQLSRV_ASSERT( false, "Invalid type for an output parameter." );
break;
default:
SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." );
break;
}
CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) {
throw core::CoreException();
}
}
// if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so
// when the buffer is filled, the type is correct
if( direction == SQL_PARAM_OUTPUT ) {
switch( php_out_type ) {
case SQLSRV_PHPTYPE_INT:
convert_to_long( param_z );
break;
case SQLSRV_PHPTYPE_FLOAT:
convert_to_double( param_z );
break;
case SQLSRV_PHPTYPE_STRING:
convert_to_string( param_z );
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
SQLSRV_ASSERT( false, "Invalid type for an output parameter" );
break;
default:
SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" );
break;
}
}
SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) ||
( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 ||
encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" );
// if the sql type is unknown, then set the default based on the PHP type passed in
if( sql_type == SQL_UNKNOWN_TYPE ) {
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
}
// if the size is unknown, then set the default based on the PHP type passed in
if( column_size == SQLSRV_UNKNOWN_SIZE ) {
default_sql_size_and_scale( stmt, param_num, param_z, encoding, column_size, decimal_digits TSRMLS_CC );
}
// determine the ODBC C type
c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC );
// set the buffer based on the PHP parameter type
switch( Z_TYPE_P( param_z )) {
case IS_NULL:
{
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
ind_ptr = SQL_NULL_DATA;
buffer = NULL;
buffer_len = 0;
}
break;
case IS_BOOL:
case IS_LONG:
{
buffer = &param_z->value;
buffer_len = sizeof( param_z->value.lval );
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ) {
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
sqlsrv_output_param output_param( param_z, param_num, zval_was_bool );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
}
}
break;
case IS_DOUBLE:
{
buffer = &param_z->value;
buffer_len = sizeof( param_z->value.dval );
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ) {
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
sqlsrv_output_param output_param( param_z, param_num, false );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
}
}
break;
case IS_STRING:
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) {
zval_auto_ptr wbuffer_z;
ALLOC_INIT_ZVAL( wbuffer_z );
bool converted = convert_input_param_to_utf16( param_z, wbuffer_z );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
param_num + 1, get_last_error_message() ) {
throw core::CoreException();
}
buffer = Z_STRVAL_P( wbuffer_z );
buffer_len = Z_STRLEN_P( wbuffer_z );
core::sqlsrv_add_index_zval( *stmt, stmt->param_input_strings, param_num, wbuffer_z TSRMLS_CC );
wbuffer_z.transferred();
}
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ) {
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4
// PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion,
// we reallocate the string if it's interned
if( IS_INTERNED( buffer )) {
ZVAL_STRINGL( param_z, static_cast<const char*>(buffer), buffer_len, 1 );
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
}
#endif
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
// convert it to wchar first
if( direction == SQL_PARAM_INPUT_OUTPUT &&
(c_type == SQL_C_WCHAR ||
(c_type == SQL_C_BINARY &&
(sql_type == SQL_WCHAR ||
sql_type == SQL_WVARCHAR ||
sql_type == SQL_WLONGVARCHAR )))) {
bool converted = convert_input_param_to_utf16( param_z, param_z );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
param_num + 1, get_last_error_message() ) {
throw core::CoreException();
}
sqlsrv_free( buffer );
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
ind_ptr = buffer_len;
}
// since this is an output string, assure there is enough space to hold the requested size and
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size,
buffer, buffer_len TSRMLS_CC );
// save the parameter to be adjusted and/or converted after the results are processed
sqlsrv_output_param output_param( param_z, encoding, param_num, buffer_len );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
// For output parameters, if we set the column_size to be same as the buffer_len,
// than if there is a truncation due to the data coming from the server being
// greater than the column_size, we don't get any truncation error. In order to
// avoid this silent truncation, we set the column_size to be "MAX" size for
// string types. This will guarantee that there is no silent truncation for
// output parameters.
if( direction == SQL_PARAM_OUTPUT ) {
switch( sql_type ) {
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
column_size = SQL_SS_LENGTH_UNLIMITED;
break;
default:
break;
}
}
}
break;
case IS_RESOURCE:
{
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
sqlsrv_stream stream_encoding( param_z, encoding );
HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams );
core::sqlsrv_zend_hash_index_update( *stmt, streams_ht, param_num, &stream_encoding, sizeof( stream_encoding )
TSRMLS_CC );
buffer = reinterpret_cast<SQLPOINTER>( param_num );
zval_add_ref( &param_z ); // so that it doesn't go away while we're using it
buffer_len = 0;
ind_ptr = SQL_DATA_AT_EXEC;
}
break;
case IS_OBJECT:
{
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
zval_auto_ptr function_z;
zval_auto_ptr buffer_z;
zval_auto_ptr format_z;
zval* params[1];
bool valid_class_name_found = false;
zend_class_entry *class_entry = zend_get_class_entry( param_z TSRMLS_CC );
while( class_entry != NULL ) {
if( class_entry->name_length == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL &&
stricmp( class_entry->name, DateTime::DATETIME_CLASS_NAME ) == 0 ) {
valid_class_name_found = true;
break;
}
else {
// Check the parent
class_entry = class_entry->parent;
}
}
CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
throw core::CoreException();
}
ALLOC_INIT_ZVAL( buffer_z );
ALLOC_INIT_ZVAL( function_z );
ALLOC_INIT_ZVAL( format_z );
// if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
// sql type, it lacks the timezone.
if( sql_type == SQL_SS_TIMESTAMPOFFSET ) {
ZVAL_STRINGL( format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
DateTime::DATETIMEOFFSET_FORMAT_LEN, 1 /* dup */ );
}
else if( sql_type == SQL_TYPE_DATE ) {
ZVAL_STRINGL( format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN, 1 /* dup */ );
}
else {
ZVAL_STRINGL( format_z, const_cast<char*>( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN,
1 /* dup */);
}
// call the DateTime::format member function to convert the object to a string that SQL Server understands
ZVAL_STRINGL( function_z, "format", sizeof( "format" ) - 1, 1 );
params[0] = format_z;
// This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the
// DateTime object and $format_z is the format string.
int zr = call_user_function( EG( function_table ), &param_z, function_z, buffer_z, 1, params TSRMLS_CC );
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
throw core::CoreException();
}
buffer = Z_STRVAL_P( buffer_z );
zr = add_next_index_zval( stmt->param_datetime_buffers, buffer_z );
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
throw core::CoreException();
}
buffer_len = Z_STRLEN_P( buffer_z );
buffer_z.transferred();
ind_ptr = buffer_len;
break;
}
case IS_ARRAY:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 );
break;
default:
DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. "
"It is the responsibilty of the driver layer to convert a parameter to one of these types." );
break;
}
if( zval_was_null ) {
ind_ptr = SQL_NULL_DATA;
}
core::SQLBindParameter( stmt, param_num + 1, direction, c_type, sql_type, column_size, decimal_digits, buffer, buffer_len,
&ind_ptr TSRMLS_CC );
}
catch( core::CoreException& e ) {
stmt->free_param_data( TSRMLS_C );
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
throw e;
}
}
// core_sqlsrv_execute
// Executes the statement previously prepared
// Parameters:
// stmt - the core sqlsrv_stmt structure that contains the ODBC handle
// Return:
// true if there is data, false if there is not
void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len )
{
try {
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
SQLRETURN r;
if( sql ) {
sqlsrv_malloc_auto_ptr<wchar_t> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( sizeof( wchar_t )));
wsql_string[0] = L'\0';
wsql_len = 0;
}
else {
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
stmt->encoding() );
wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast<const char*>( sql ),
sql_len, &wsql_len );
CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
get_last_error_message() ) {
throw core::CoreException();
}
}
r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC );
}
else {
r = core::SQLExecute( stmt TSRMLS_CC );
}
// if data is needed (streams were bound) and they should be sent at execute time, then do so now
if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) {
send_param_streams( stmt TSRMLS_CC );
}
stmt->new_result_set( TSRMLS_C );
stmt->executed = true;
// if all the data has been sent and no data was returned then finalize the output parameters
if( stmt->send_streams_at_exec && (r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) {
finalize_output_parameters( stmt TSRMLS_CC );
}
}
catch( core::CoreException& e ) {
// if the statement executed but failed in a subsequent operation before returning,
// we need to cancel the statement
if( stmt->executed ) {
SQLCancel( stmt->handle() );
// stmt->executed = false; should this be reset if something fails?
}
throw e;
}
}
// core_sqlsrv_fetch
// Moves the cursor according to the parameters (by default, moves to the next row)
// Parameters:
// stmt - the sqlsrv_stmt of the cursor
// fetch_orientation - method to move the cursor
// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number)
// Returns:
// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the
// user scrolls past a non-scrollable result set
bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC )
{
// pre-condition check
SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE,
"core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." );
try {
// clear the field cache of the previous fetch
zend_hash_clean( Z_ARRVAL_P( stmt->field_cache ));
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) {
throw core::CoreException();
}
SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC );
CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) {
throw core::CoreException();
}
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
// if the statement has rows and is not scrollable but doesn't yet have
// fetch_called, this must be the first time we've called sqlsrv_fetch.
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) {
stmt->fetch_called = true;
return true;
}
// move to the record requested. For absolute records, we use a 0 based offset, so +1 since
// SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided.
SQLRETURN r = stmt->current_results->fetch( fetch_orientation,
( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1
TSRMLS_CC );
if( r == SQL_NO_DATA ) {
// if this is a forward only cursor, mark that we've passed the end so future calls result in an error
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) {
stmt->past_fetch_end = true;
}
return false;
}
// mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved
stmt->fetch_called = true;
stmt->last_field_index = -1;
stmt->has_rows = true; // since we made it this far, we must have at least one row
}
catch (core::CoreException& e) {
throw e;
}
catch ( ... ) {
DIE( "core_sqlsrv_fetch: Unexpected exception occurred." );
}
return true;
}
// Retrieves metadata for a field of a prepared statement.
// Parameters:
// colno - the index of the field for which to return the metadata. columns are 0 based in PDO
// Return:
// A field_meta_data* consisting of the field metadata.
field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC )
{
// pre-condition check
SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." );
sqlsrv_malloc_auto_ptr<field_meta_data> meta_data;
SQLSMALLINT field_name_len = 0;
meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data();
meta_data->field_name = static_cast<SQLCHAR*>( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 ));
try {
core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len,
&(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale),
&(meta_data->field_is_nullable) TSRMLS_CC );
}
catch( core::CoreException& e ) {
throw e;
}
// depending on field type, we add the values into size or precision/scale.
switch( meta_data->field_type ) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_BIT:
case SQL_TINYINT:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_BIGINT:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
{
meta_data->field_precision = meta_data->field_size;
meta_data->field_size = 0;
break;
}
default: {
break;
}
}
// Set the field name lenth
meta_data->field_name_len = field_name_len;
field_meta_data* result_field_meta_data = meta_data;
meta_data.transferred();
return result_field_meta_data;
}
// core_sqlsrv_get_field
// Return the value of a column from ODBC
// Parameters:
// stmt - the sqlsrv_stmt from which to retrieve the column
// field_index - 0 based index for the column to retrieve
// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in
// field_value - pointer to the data retrieved
// field_len - length of the data in the field_value buffer
// Returns:
// Nothing, excpetion thrown if an error occurs
void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string,
__out void** field_value, __out SQLLEN* field_len, bool cache_field,
__out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC )
{
try {
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
// if the field has been retrieved before, return the previous result
field_cache* cached = NULL;
if( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), field_index, (void**) &cached ) == SUCCESS ) {
// the field value is NULL
if( cached->value == NULL ) {
*field_value = NULL;
*field_len = 0;
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; }
}
else {
*field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 );
memcpy( *field_value, cached->value, cached->len );
if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING ) {
// prevent the 'string not null terminated' warning
reinterpret_cast<char*>( *field_value )[ cached->len ] = '\0';
}
*field_len = cached->len;
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( cached->type.typeinfo.type ); }
}
return;
}
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
// Make sure that the statement was executed and not just prepared.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
// may also be retrieved.
if( cache_field && ( field_index - stmt->last_field_index ) >= 2 ) {
sqlsrv_phptype invalid;
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
SQLSRV_ASSERT( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), i, (void**) &cached ) == FAILURE,
"Field already cached." );
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field,
sqlsrv_php_type_out TSRMLS_CC );
// delete the value returned since we only want it cached, not the actual value
if( *field_value ) {
efree( *field_value );
*field_value = NULL;
*field_len = 0;
}
}
}
// If the php type was not specified set the php type to be the default type.
if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) {
// Get the SQL type of the field.
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
// Get the length of the field.
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC );
// Get the corresponding php type from the sql type.
sqlsrv_php_type = stmt->sql_type_to_php_type( sql_field_type, sql_field_len, prefer_string );
}
// Verify that we have an acceptable type to convert.
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
throw core::CoreException();
}
if( sqlsrv_php_type_out != NULL )
*sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( sqlsrv_php_type.typeinfo.type );
// Retrieve the data
core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
// if the user wants us to cache the field, we'll do it
if( cache_field ) {
field_cache cache( *field_value, *field_len, sqlsrv_php_type );
core::sqlsrv_zend_hash_index_update( *stmt, Z_ARRVAL_P( stmt->field_cache ), field_index, &cache,
sizeof( field_cache ) TSRMLS_CC );
}
}
catch( core::CoreException& e) {
throw e;
}
}
// core_sqlsrv_has_any_result
// return if any result set or rows affected message is waiting
// to be consumed and moved over by sqlsrv_next_result.
// Parameters:
// stmt - The statement object on which to check for results.
// Return:
// true if any results are present, false otherwise.
bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC )
{
// Use SQLNumResultCols to determine if we have rows or not.
SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
// use SQLRowCount to determine if there is a rows status waiting
SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC );
return (num_cols != 0) || (rows_affected > 0);
}
// core_sqlsrv_next_result
// Advances to the next result set from the last executed query
// Parameters
// stmt - the sqlsrv_stmt structure
// Returns
// Nothing, exception thrown if problem occurs
void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors )
{
try {
// make sure that the statement has been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) {
throw core::CoreException();
}
close_active_stream( stmt TSRMLS_CC );
SQLRETURN r;
if( throw_on_errors ) {
r = core::SQLMoreResults( stmt TSRMLS_CC );
}
else {
r = SQLMoreResults( stmt->handle() );
}
if( r == SQL_NO_DATA ) {
if( stmt->output_params && finalize_output_params ) {
// if we're finished processing result sets, handle the output parameters
finalize_output_parameters( stmt TSRMLS_CC );
}
// mark we are past the end of all results
stmt->past_next_result_end = true;
return;
}
stmt->new_result_set( TSRMLS_C );
}
catch( core::CoreException& e ) {
SQLCancel( stmt->handle() );
throw e;
}
}
// core_sqlsrv_post_param
// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement
// Parameters:
// stmt - the sqlsrv_stmt structure
// param_num - 0 based index of the parameter
// param_z - parameter value itself.
// Returns:
// Nothing, exception thrown if problem occurs
void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int param_num, zval* param_z TSRMLS_DC )
{
SQLSRV_ASSERT( Z_TYPE_P( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." );
SQLSRV_ASSERT( Z_TYPE_P( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." );
// if the parameter was an input string, delete it from the array holding input parameter strings
if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_input_strings ), param_num )) {
core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_input_strings ), param_num TSRMLS_CC );
}
// if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams
// PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it
// with sqlsrv_send_stream_data.
if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_streams ), param_num )) {
sqlsrv_stream* stream_encoding;
zend_hash_index_find( Z_ARRVAL_P( stmt->param_streams ), param_num, (void**) &stream_encoding );
core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_streams ), param_num TSRMLS_CC );
}
}
//Calls SQLSetStmtAttr to set a cursor.
void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC )
{
try {
switch( cursor_type ) {
case SQL_CURSOR_STATIC:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQL_CURSOR_DYNAMIC:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQL_CURSOR_KEYSET_DRIVEN:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQL_CURSOR_FORWARD_ONLY:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQLSRV_CURSOR_BUFFERED:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC );
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
break;
}
stmt->cursor_type = cursor_type;
}
catch( core::CoreException& ) {
throw;
}
}
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
}
core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC );
}
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC )
{
if( limit <= 0 ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
}
stmt->buffered_query_limit = limit;
}
// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout
// which accepts timeout parameter as a long. If the zval is not of type long
// than throws error.
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
try {
// validate the value
if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) {
convert_to_string( value_z );
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) );
}
core_sqlsrv_set_query_timeout( stmt, Z_LVAL_P( value_z ) TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
// Overloaded. Accepts the timeout as a long.
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." );
// set the statement attribute
core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( timeout ), SQL_IS_UINTEGER TSRMLS_CC );
// a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which
// is represented by -1.
long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ );
// set the LOCK_TIMEOUT on the server.
char lock_timeout_sql[ 32 ];
int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d",
lock_timeout );
SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )),
"stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." );
core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC );
stmt->query_timeout = timeout;
}
catch( core::CoreException& ) {
throw;
}
}
void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
TSRMLS_C;
// zend_is_true does not fail. It either returns true or false.
stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false;
}
// core_sqlsrv_send_stream_packet
// send a single packet from a stream parameter to the database using
// ODBC. This will also handle the transition between parameters. It
// returns true if it is not done sending, false if it is finished.
// return_value is what should be returned to the script if it is
// given. Any errors that occur are posted here.
// Parameters:
// stmt - query to send the next packet for
// Returns:
// true if more data remains to be sent, false if all data processed
bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
// if there no current parameter to process, get the next one
// (probably because this is the first call to sqlsrv_send_stream_data)
if( stmt->current_stream.stream_z == NULL ) {
if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) {
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
stmt->current_stream_read = 0;
return false;
}
}
try {
// get the stream from the zval we bound
php_stream* param_stream = NULL;
core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, &stmt->current_stream.stream_z TSRMLS_CC );
// if we're at the end, then release our current parameter
if( php_stream_eof( param_stream )) {
// if no data was actually sent prior, then send a NULL
if( stmt->current_stream_read == 0 ) {
// send an empty string, which is what a 0 length does.
char buff[1]; // temp storage to hand to SQLPutData
core::SQLPutData( stmt, buff, 0 TSRMLS_CC );
}
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
stmt->current_stream_read = 0;
}
// read the data from the stream, send it via SQLPutData and track how much we've sent.
else {
char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character
size_t read = php_stream_read( param_stream, buffer, buffer_size );
stmt->current_stream_read += read;
if( read > 0 ) {
// if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
// twice.
// If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
// since all other MBCS supported by SQL Server are 2 byte maximum size.
if( stmt->current_stream.encoding == CP_UTF8 ) {
// the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a
// expansion of 2x the UTF-8 size.
wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS,
buffer, read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t ));
if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) {
// this will calculate how many bytes were cut off from the last UTF-8 character and read that many more
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC );
// read the missing bytes
size_t new_read = php_stream_read( param_stream, static_cast<char*>( buffer ) + read,
need_to_read );
// if the bytes couldn't be read, then we return an error
CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) {
throw core::CoreException();
}
// try the conversion again with the complete character
wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS,
buffer, read + new_read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t ));
// something else must be wrong if it failed
CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) {
throw core::CoreException();
}
}
core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC );
}
else {
core::SQLPutData( stmt, buffer, read TSRMLS_CC );
}
}
}
}
catch( core::CoreException& e ) {
stmt->free_param_data( TSRMLS_C );
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
SQLCancel( stmt->handle() );
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT );
stmt->current_stream_read = 0;
throw e;
}
return true;
}
void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC )
{
TSRMLS_C;
// This implementation should never get called.
DIE( "Not implemented." );
}
void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC )
{
core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC );
}
void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC );
}
void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC );
}
// internal function to release the active stream. Called by each main API function
// that will alter the statement and cancel any retrieval of data from a stream.
void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC )
{
// if there is no active stream, return
if( stmt->active_stream == NULL ) {
return;
}
php_stream* stream = NULL;
// we use no verify since verify would return immediately and we want to assert, not return.
php_stream_from_zval_no_verify( stream, &stmt->active_stream );
SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." );
php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here.
SQLSRV_ASSERT( stmt->active_stream == NULL, "close_active_stream: Active stream not closed." );
}
// local routines not shared by other files (arranged alphabetically)
namespace {
bool is_streamable_type( SQLINTEGER sql_type )
{
switch( sql_type ) {
case SQL_CHAR:
case SQL_WCHAR:
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_SS_XML:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
return true;
}
return false;
}
void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC )
{
try {
switch( sql_type ) {
// for types that are fixed in size or for which the size is unknown, return the display size.
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_GUID:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_REAL:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_BINARY:
case SQL_CHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_SS_XML:
case SQL_SS_UDT:
case SQL_WLONGVARCHAR:
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
{
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC );
break;
}
// for wide char types for which the size is known, return the octet length instead, since it will include the
// the number of bytes necessary for the string, not just the characters
case SQL_WCHAR:
case SQL_WVARCHAR:
{
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC );
break;
}
default:
DIE ( "Unexpected SQL type encountered in calc_string_size." );
}
}
catch( core::CoreException& e ) {
throw e;
}
}
// calculates how many characters were cut off from the end of a buffer when reading
// in UTF-8 encoded text
size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC )
{
const char* last_char = buffer + buffer_end - 1;
size_t need_to_read = 0;
// rewind until we are at the byte that starts the cut off character
while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) {
--last_char;
++need_to_read;
}
// determine how many bytes we need to read in based on the number of bytes in the character
// (# of high bits set) versus the number of bytes we've already read.
switch( *last_char & UTF8_NBYTESEQ_MASK ) {
case UTF8_2BYTESEQ_TAG1:
case UTF8_2BYTESEQ_TAG2:
need_to_read = 1 - need_to_read;
break;
case UTF8_3BYTESEQ_TAG:
need_to_read = 2 - need_to_read;
break;
case UTF8_4BYTESEQ_TAG:
need_to_read = 3 - need_to_read;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION ));
break;
}
return need_to_read;
}
// Caller is responsible for freeing the memory allocated for the field_value.
// The memory allocation has to happen in the core layer because otherwise
// the driver layer would have to calculate size of the field_value
// to decide the amount of memory allocation.
void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype
sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC )
{
try {
close_active_stream( stmt TSRMLS_CC );
// make sure that fetch is called before trying to retrieve.
CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) {
throw core::CoreException();
}
// make sure that fields are not retrieved incorrectly.
CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index,
stmt->last_field_index ) {
throw core::CoreException();
}
switch( sqlsrv_php_type.typeinfo.type ) {
case SQLSRV_PHPTYPE_INT:
{
sqlsrv_malloc_auto_ptr<long> field_value_temp;
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
field_len, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
if( *field_len == SQL_NULL_DATA ) {
*field_value = NULL;
break;
}
*field_value = field_value_temp;
field_value_temp.transferred();
break;
}
case SQLSRV_PHPTYPE_FLOAT:
{
sqlsrv_malloc_auto_ptr<double> field_value_temp;
field_value_temp = static_cast<double*>( sqlsrv_malloc( sizeof( double )));
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ),
field_len, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( *field_len == SQL_NULL_DATA ) {
*field_value = NULL;
break;
}
*field_value = field_value_temp;
field_value_temp.transferred();
break;
}
case SQLSRV_PHPTYPE_STRING:
{
get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
break;
}
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and
// convert it to a DateTime object and return the created object
case SQLSRV_PHPTYPE_DATETIME:
{
char field_value_temp[ MAX_DATETIME_STRING_LEN ];
zval_auto_ptr field_value_temp_z;
zval_auto_ptr return_value_z;
zval_auto_ptr function_z;
zval* params[1];
ALLOC_INIT_ZVAL( field_value_temp_z );
ALLOC_INIT_ZVAL( function_z );
ALLOC_INIT_ZVAL( return_value_z );
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp,
MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC );
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( *field_len == SQL_NULL_DATA ) {
ZVAL_NULL( return_value_z );
*field_value = reinterpret_cast<void*>( return_value_z.get() );
return_value_z.transferred();
break;
}
// Convert the string date to a DateTime object
ZVAL_STRINGL( field_value_temp_z, field_value_temp, *field_len, 1 );
ZVAL_STRINGL( function_z, "date_create", sizeof("date_create") -1, 1 );
params[0] = field_value_temp_z;
if( call_user_function( EG( function_table ), NULL, function_z, return_value_z, 1,
params TSRMLS_CC ) == FAILURE ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED );
}
*field_value = reinterpret_cast<void*>( return_value_z.get() );
return_value_z.transferred();
break;
}
// create a stream wrapper around the field and return that object to the PHP script. calls to fread
// on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file
// for how these fields are used.
case SQLSRV_PHPTYPE_STREAM:
{
zval_auto_ptr return_value_z;
php_stream* stream = NULL;
sqlsrv_stream* ss = NULL;
ALLOC_INIT_ZVAL( return_value_z );
SQLINTEGER sql_type;
SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
throw core::CoreException();
}
stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL );
CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) {
throw core::CoreException();
}
ss = static_cast<sqlsrv_stream*>( stream->abstract );
ss->stmt = stmt;
ss->field_index = field_index;
ss->sql_type = static_cast<SQLUSMALLINT>( sql_type );
ss->encoding = static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding );
// turn our stream into a zval to be returned
php_stream_to_zval( stream, return_value_z );
// mark this as our active stream
stmt->active_stream = return_value_z;
*field_value = reinterpret_cast<void*>( return_value_z.get() );
return_value_z.transferred();
break;
}
case SQLSRV_PHPTYPE_NULL:
*field_value = NULL;
*field_len = 0;
break;
default:
DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" );
break;
}
// sucessfully retrieved the field, so update our last retrieved field
if( stmt->last_field_index < field_index ) {
stmt->last_field_index = field_index;
}
}
catch( core::CoreException& e ) {
throw e;
}
}
// check_for_next_stream_parameter
// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise
// returns false
bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC )
{
int stream_index = 0;
SQLRETURN r = SQL_SUCCESS;
sqlsrv_stream* stream_encoding;
zval* param_z = NULL;
// get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param
r = core::SQLParamData( stmt, reinterpret_cast<SQLPOINTER*>( &stream_index ) TSRMLS_CC );
// if no more data, we've exhausted the bound parameters, so return that we're done
if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
// we're all done, so return false
return false;
}
HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams );
// pull out the sqlsrv_encoding struct
int zr = zend_hash_index_find( streams_ht, stream_index, (void**) &stream_encoding );
SQLSRV_ASSERT( zr == SUCCESS, "Stream parameter does not exist" ); // if the index isn't in the hash, that's a serious error
param_z = stream_encoding->stream_z;
// make the next stream current
stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding );
stmt->current_stream_read = 0;
// there are more parameters
return true;
}
// utility routine to convert an input parameter from UTF-8 to UTF-16
bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z )
{
SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL,
"convert_input_param_z called with invalid parameter states" );
const char* buffer = Z_STRVAL_P( input_param_z );
int buffer_len = Z_STRLEN_P( input_param_z );
int wchar_size;
// if the string is empty, then just return that the conversion succeeded as
// MultiByteToWideChar will "fail" on an empty string.
if( buffer_len == 0 ) {
ZVAL_STRINGL( converted_param_z, "", 0, 1 );
return true;
}
// if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string
wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), buffer_len, NULL, 0 );
// if there was a problem determining the size of the string, return false
if( wchar_size == 0 ) {
return false;
}
sqlsrv_malloc_auto_ptr<wchar_t> wbuffer;
wbuffer = reinterpret_cast<wchar_t*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) ));
// convert the utf-8 string to a wchar string in the new buffer
int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ),
buffer_len, wbuffer, wchar_size );
// if there was a problem converting the string, then free the memory and return false
if( r == 0 ) {
return false;
}
// null terminate the string, set the size within the zval, and return success
wbuffer[ wchar_size ] = L'\0';
ZVAL_STRINGL( converted_param_z, reinterpret_cast<char*>( wbuffer.get() ),
wchar_size * sizeof( wchar_t ), 0 );
wbuffer.transferred();
return true;
}
// returns the ODBC C type constant that matches the PHP type and encoding given
SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC )
{
SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE;
int php_type = Z_TYPE_P( param_z );
switch( php_type ) {
case IS_NULL:
switch( encoding ) {
// The c type is set to match to the corresponding sql_type. For NULL cases, if the server type
// is a binary type, than the server expects the sql_type to be binary type as well, otherwise
// an error stating "Implicit conversion not allowed.." is thrown by the server.
// For all other server types, setting the sql_type to sql_char works fine.
case SQLSRV_ENCODING_BINARY:
sql_c_type = SQL_C_BINARY;
break;
default:
sql_c_type = SQL_C_CHAR;
break;
}
break;
case IS_BOOL:
case IS_LONG:
sql_c_type = SQL_C_LONG;
break;
case IS_DOUBLE:
sql_c_type = SQL_C_DOUBLE;
break;
case IS_STRING:
case IS_RESOURCE:
switch( encoding ) {
case SQLSRV_ENCODING_CHAR:
sql_c_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_c_type = SQL_C_BINARY;
break;
case CP_UTF8:
sql_c_type = SQL_C_WCHAR;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno );
break;
}
break;
// it is assumed that an object is a DateTime since it's the only thing we support.
// verification that it's a real DateTime object occurs in core_sqlsrv_bind_param.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
sql_c_type = SQL_C_CHAR;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
break;
}
return sql_c_type;
}
// given a zval and encoding, determine the appropriate sql type
void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLSMALLINT& sql_type TSRMLS_DC )
{
sql_type = SQL_UNKNOWN_TYPE;
int php_type = Z_TYPE_P( param_z );
switch( php_type ) {
case IS_NULL:
switch( encoding ) {
// Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases,
// if the server type is a binary type, than the server expects the sql_type to be binary type
// as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the
// server. For all other server types, setting the sql_type to sql_char works fine.
case SQLSRV_ENCODING_BINARY:
sql_type = SQL_BINARY;
break;
default:
sql_type = SQL_CHAR;
break;
}
break;
case IS_BOOL:
case IS_LONG:
sql_type = SQL_INTEGER;
break;
case IS_DOUBLE:
sql_type = SQL_FLOAT;
break;
case IS_RESOURCE:
case IS_STRING:
switch( encoding ) {
case SQLSRV_ENCODING_CHAR:
sql_type = SQL_VARCHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_type = SQL_VARBINARY;
break;
case CP_UTF8:
sql_type = SQL_WVARCHAR;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno );
break;
}
break;
// it is assumed that an object is a DateTime since it's the only thing we support.
// verification that it's a real DateTime object occurs in the calling function.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
// since these are the date types of the highest precision for their respective server versions
if( stmt->conn->server_version <= SERVER_VERSION_2005 ) {
sql_type = SQL_TYPE_TIMESTAMP;
}
else {
sql_type = SQL_SS_TIMESTAMPOFFSET;
}
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
break;
}
}
// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate)
void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC )
{
int php_type = Z_TYPE_P( param_z );
column_size = 0;
decimal_digits = 0;
switch( php_type ) {
case IS_NULL:
column_size = 1;
break;
// size is not necessary for these types, they are inferred by ODBC
case IS_BOOL:
case IS_LONG:
case IS_DOUBLE:
case IS_RESOURCE:
break;
case IS_STRING:
{
SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char ));
if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) {
column_size = SQL_SERVER_MAX_TYPE_SIZE;
}
else {
column_size = Z_STRLEN_P( param_z );
}
break;
}
// it is assumed that an object is a DateTime since it's the only thing we support.
// verification that it's a real DateTime object occurs in the calling function.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
// since these are the date types of the highest precision for their respective server versions
if( stmt->conn->server_version <= SERVER_VERSION_2005 ) {
column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION;
decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE;
}
else {
column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION;
decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE;
}
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
break;
}
}
void field_cache_dtor( void* data )
{
field_cache* cache = reinterpret_cast<field_cache*>( data );
if( cache->value )
{
sqlsrv_free( cache->value );
}
}
// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output
// parameters will be present until all results are processed (since output parameters can depend on results
// while being processed). This function updates the lengths of output parameter strings from the ind_ptr
// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary.
// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server
void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC )
{
if( stmt->output_params == NULL )
return;
bool converted = true;
HashTable* params_ht = Z_ARRVAL_P( stmt->output_params );
for( zend_hash_internal_pointer_reset( params_ht );
zend_hash_has_more_elements( params_ht ) == SUCCESS;
zend_hash_move_forward( params_ht ) ) {
sqlsrv_output_param *output_param;
core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &output_param TSRMLS_CC );
switch( Z_TYPE_P( output_param->param_z )) {
case IS_STRING:
{
// adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter
char* str = Z_STRVAL_P( output_param->param_z );
SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ];
if( str_len == SQL_NULL_DATA ) {
ZVAL_NULL( output_param->param_z );
continue;
}
// if there was more to output than buffer size to hold it, then throw a truncation error
int null_size = 0;
switch( output_param->encoding ) {
case SQLSRV_ENCODING_UTF8:
null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16
break;
case SQLSRV_ENCODING_SYSTEM:
null_size = 1;
break;
case SQLSRV_ENCODING_BINARY:
null_size = 0;
break;
default:
SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." );
break;
}
CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt,
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) {
throw core::CoreException();
}
// if it's not in the 8 bit encodings, then it's in UTF-16
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
bool converted = convert_string_from_utf16_inplace( output_param->encoding, &str, str_len );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
// so we do that here if the length of the returned data is less than the original allocation. The
// original allocation null terminates the buffer already.
str[ str_len ] = '\0';
}
// set the string length
ZVAL_STRINGL( output_param->param_z, str, str_len, 0 );
}
break;
case IS_LONG:
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
ZVAL_NULL( output_param->param_z );
}
else if( output_param->is_bool ) {
convert_to_boolean( output_param->param_z );
}
break;
case IS_DOUBLE:
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
ZVAL_NULL( output_param->param_z );
}
break;
default:
DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." );
break;
}
}
// empty the hash table since it's been processed
zend_hash_clean( Z_ARRVAL_P( stmt->output_params ));
return;
}
void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type,
__out void** field_value, __out SQLLEN* field_len TSRMLS_DC )
{
SQLRETURN r;
SQLSMALLINT c_type;
SQLLEN sql_field_type = 0;
SQLSMALLINT extra = 0;
SQLLEN field_len_temp;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
try {
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
}
// Set the C type and account for null characters at the end of the data.
switch( sqlsrv_php_type.typeinfo.encoding ) {
case CP_UTF8:
c_type = SQL_C_WCHAR;
extra = sizeof( SQLWCHAR );
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
extra = 0;
break;
default:
c_type = SQL_C_CHAR;
extra = sizeof( SQLCHAR );
break;
}
// Get the SQL type of the field.
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
// Calculate the field size.
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
// if this is a large type, then read the first few bytes to get the actual length from SQLGetData
if( sql_display_size == 0 || sql_display_size == LONG_MAX ||
sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) {
field_len_temp = INITIAL_FIELD_STRING_LEN;
field_value_temp = static_cast<char*>( sqlsrv_malloc( field_len_temp + extra + 1 ));
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ),
&field_len_temp, false /*handle_warning*/ TSRMLS_CC );
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( field_len_temp == SQL_NULL_DATA ) {
*field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
if( r == SQL_SUCCESS_WITH_INFO ) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
if( is_truncated_warning( state ) ) {
SQLINTEGER dummy_field_len;
// for XML (and possibly other conditions) the field length returned is not the real field length, so
// in every pass, we double the allocation size to retrieve all the contents.
if( field_len_temp == SQL_NO_TOTAL ) {
// reset the field_len_temp
field_len_temp = INITIAL_FIELD_STRING_LEN;
do {
SQLINTEGER initial_field_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
field_len_temp -= initial_field_len;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len,
field_len_temp + extra, &dummy_field_len,
false /*handle_warning*/ TSRMLS_CC );
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
// so we calculate the actual length of the string with that.
if( dummy_field_len != SQL_NO_TOTAL )
field_len_temp += dummy_field_len;
else
field_len_temp += initial_field_len;
if( r == SQL_SUCCESS_WITH_INFO ) {
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
TSRMLS_CC );
}
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
}
else {
// We got the field_len_temp from SQLGetData call.
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
// We have already recieved INITIAL_FIELD_STRING_LEN size data.
field_len_temp -= INITIAL_FIELD_STRING_LEN;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN,
field_len_temp + extra, &dummy_field_len,
true /*handle_warning*/ TSRMLS_CC );
if( dummy_field_len == SQL_NULL_DATA ) {
*field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
field_len_temp += INITIAL_FIELD_STRING_LEN;
}
} // if( is_truncation_warning ( state ) )
else {
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
}
} // if( r == SQL_SUCCESS_WITH_INFO )
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) {
throw core::CoreException ();
}
}
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) {
// only allow binary retrievals for char and binary types. All others get a string converted
// to the encoding type they asked for.
// null terminator
if( c_type == SQL_C_CHAR ) {
sql_display_size += sizeof( SQLCHAR );
}
// For WCHAR multiply by sizeof(WCHAR) and include the null terminator
else if( c_type == SQL_C_WCHAR ) {
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR);
}
field_value_temp = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
// get the data
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size,
&field_len_temp, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( field_len_temp == SQL_NULL_DATA ) {
*field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) {
throw core::CoreException ();
}
}
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
else {
DIE( "Invalid sql_display_size" );
return; // to eliminate a warning
}
*field_value = field_value_temp;
*field_len = field_len_temp;
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
// operator to set add 1 to fill the null terminator
field_value_temp[field_len_temp] = '\0';
}
catch( core::CoreException& ) {
*field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
throw;
}
catch ( ... ) {
*field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
throw;
}
}
// return the option from the stmt_opts array that matches the key. If no option found,
// NULL is returned.
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC )
{
for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) {
// if we find the key we're looking for, return it
if( key == stmt_opts[ i ].key ) {
return &stmt_opts[ i ];
}
}
return NULL; // no option found
}
// is_fixed_size_type
// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary
bool is_fixed_size_type( SQLINTEGER sql_type )
{
switch( sql_type ) {
case SQL_BINARY:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
case SQL_SS_XML:
case SQL_SS_UDT:
return false;
}
return true;
}
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type )
{
switch( type.typeinfo.type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
{
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
return true;
}
break;
}
}
return false;
}
// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z
// is updated to have the new buffer with the correct size and its reference is incremented. The output
// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and
// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter
void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding,
SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer,
SQLLEN& buffer_len TSRMLS_DC )
{
SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." );
buffer_len = Z_STRLEN_P( param_z );
buffer = Z_STRVAL_P( param_z );
SQLLEN expected_len;
SQLLEN buffer_null_extra;
SQLLEN elem_size;
SQLLEN without_null_len;
// calculate the size of each 'element' represented by column_size. WCHAR is of course 2,
// as is a n(var)char/ntext field being returned as a binary field.
elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1;
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
expected_len = column_size * elem_size + elem_size;
// binary fields aren't null terminated, so we need to account for that in our buffer length calcuations
buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0;
// this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter
without_null_len = column_size * elem_size;
// increment to include the null terminator since the Zend length doesn't include the null terminator
buffer_len += elem_size;
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new
// length.
if( buffer_len < expected_len ) {
SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra,
"Integer overflow/underflow caused a corrupt field length." );
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
// not having a NULL terminator on a string.
buffer = static_cast<char*>( sqlsrv_realloc( buffer, expected_len ));
buffer_len = expected_len; // set the buffer_len to the new allocation size (includes the null terminator taken out below)
// A zval string len doesn't include the null. This calculates the length it should be
// regardless of whether the ODBC type contains the NULL or not.
ZVAL_STRINGL( param_z, reinterpret_cast<char*>( buffer ), without_null_len, 0 );
// null terminate the string to avoid a warning in debug PHP builds
(static_cast<char*>(buffer))[ without_null_len ] = '\0';
}
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
buffer_len -= buffer_null_extra;
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
// than the error 22001 is returned by ODBC.
if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) {
stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra);
}
}
// output parameters have their reference count incremented so that they do not disappear
// while the query is executed and processed. They are saved in the statement so that
// their reference count may be decremented later (after results are processed)
void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC )
{
HashTable* param_ht = Z_ARRVAL_P( stmt->output_params );
int paramno = param.param_num;
core::sqlsrv_zend_hash_index_update( *stmt, param_ht, paramno, &param, sizeof( param )
TSRMLS_CC );
zval_add_ref( &param.param_z ); // we have a reference to the param
}
// send all the stream data
void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC )
{
while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { }
}
// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed
void sqlsrv_output_param_dtor( void* data )
{
sqlsrv_output_param *output_param = reinterpret_cast<sqlsrv_output_param*>( data );
zval_ptr_dtor( &output_param->param_z ); // undo the reference to the string we will no longer hold
}
// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed
void sqlsrv_stream_dtor( void* data )
{
sqlsrv_stream* stream_encoding = reinterpret_cast<sqlsrv_stream*>( data );
zval_ptr_dtor( &stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold
}
}

View file

@ -1,263 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stream.cpp
//
// Contents: Implementation of PHP streams for reading SQL Server data
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <windows.h>
namespace {
// close a stream and free the PHP resources used by it
int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC )
{
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." );
// free the stream resources in the Zend engine
php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM );
// NULL out the stream zval and delete our reference count to it.
ZVAL_NULL( ss->stmt->active_stream );
// there is no active stream
ss->stmt->active_stream = NULL;
sqlsrv_free( ss );
stream->abstract = NULL;
return 0;
}
// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are
// set when sqlsrv_get_field is called by the user specifying which field type they want.
size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, size_t count TSRMLS_DC )
{
SQLINTEGER read = 0;
SQLSMALLINT c_type = SQL_C_CHAR;
char* get_data_buffer = buf;
sqlsrv_malloc_auto_ptr<char> temp_buf;
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." );
try {
if( stream->eof ) {
return 0;
};
switch( ss->encoding ) {
case SQLSRV_ENCODING_CHAR:
c_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
break;
case CP_UTF8:
{
c_type = SQL_C_WCHAR;
count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes
if( count > PHP_STREAM_BUFFER_SIZE ) {
count = PHP_STREAM_BUFFER_SIZE;
}
// use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16
temp_buf = static_cast<char*>( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE ));
get_data_buffer = temp_buf;
break;
}
default:
DIE( "Unknown encoding type when reading from a stream" );
break;
}
SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read );
CHECK_SQL_ERROR( r, ss->stmt ) {
stream->eof = 1;
throw core::CoreException();
}
// if the stream returns either no data, NULL data, or returns data < than the count requested then
// we are at the "end of the stream" so we mark it
if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast<size_t>( read ) <= count && read != SQL_NO_TOTAL )) {
stream->eof = 1;
}
// if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator
// if it's not a binary encoded field
if( r == SQL_SUCCESS_WITH_INFO ) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
if( read == SQL_NO_TOTAL ) {
SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it "
"did not occur." );
}
if( is_truncated_warning( state ) ) {
switch( c_type ) {
// As per SQLGetData documentation, if the length of character data exceeds the BufferLength,
// SQLGetData truncates the data to BufferLength less the length of null-termination character.
case SQL_C_BINARY:
read = count;
break;
case SQL_C_WCHAR:
read = ( count % 2 == 0 ? count - 2 : count - 3 );
break;
case SQL_C_CHAR:
read = count - 1;
break;
default:
DIE( "sqlsrv_stream_read: should have never reached in this switch case.");
break;
}
}
else {
CHECK_SQL_WARNING( r, ss->stmt );
}
}
// if the encoding is UTF-8
if( c_type == SQL_C_WCHAR ) {
count *= 2; // undo the shift to use the full buffer
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
DWORD flags = 0;
// convert to UTF-8
if( g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast<LPCWSTR>( temp_buf.get() ),
read >> 1, buf, count, NULL, NULL );
if( enc_len == 0 ) {
stream->eof = 1;
THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() );
}
read = enc_len;
}
return read;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." );
return 0;
}
}
// function table for stream operations. We only support reading and closing the stream
php_stream_ops sqlsrv_stream_ops = {
NULL,
sqlsrv_stream_read,
sqlsrv_stream_close,
NULL,
SQLSRV_STREAM,
NULL,
NULL,
NULL,
NULL
};
// open a stream and return the sqlsrv_stream_ops function table as part of the
// return value. There is only one valid way to open a stream, using sqlsrv_get_field on
// certain field types. A sqlsrv stream may only be opened in read mode.
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 6
static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in const char*, __in const char* mode,
int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC )
#else
static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in char*, __in char* mode,
int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC )
#endif
{
#if ZEND_DEBUG
SQLSRV_UNUSED( __zend_orig_lineno );
SQLSRV_UNUSED( __zend_orig_filename );
SQLSRV_UNUSED( __zend_lineno );
SQLSRV_UNUSED( __zend_filename );
SQLSRV_UNUSED( __php_stream_call_depth );
#endif
sqlsrv_malloc_auto_ptr<sqlsrv_stream> ss;
ss = static_cast<sqlsrv_stream*>( sqlsrv_malloc( sizeof( sqlsrv_stream )));
memset( ss, 0, sizeof( sqlsrv_stream ));
// check for valid options
if( options != REPORT_ERRORS ) {
php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" );
return NULL;
}
// allocate the stream from PHP
php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode );
if( php_str != NULL ) {
ss.transferred();
}
return php_str;
}
// information structure that contains PHP stream wrapper info. We supply the minimal
// possible, including the open function and the name only.
php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = {
sqlsrv_stream_opener,
NULL,
NULL,
NULL,
NULL,
SQLSRV_STREAM_WRAPPER,
NULL,
NULL,
NULL,
NULL
};
}
// structure used by PHP to get the function table for opening, closing, etc. of the stream
php_stream_wrapper g_sqlsrv_stream_wrapper = {
&sqlsrv_stream_wrapper_ops,
NULL,
0
};

View file

@ -1,358 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_util.cpp
//
// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <windows.h>
namespace {
// *** internal constants ***
log_callback g_driver_log;
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// buffer used to hold a formatted log message prior to actually logging it.
char last_err_msg[ 2048 ]; // 2k to hold the error messages
// routine used by utf16_string_from_mbcs_string
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string,
unsigned int mbcs_len,
__out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string,
unsigned int utf16_len );
}
// SQLSTATE for all internal errors
SQLCHAR IMSSP[] = "IMSSP";
// SQLSTATE for all internal warnings
SQLCHAR SSPWARN[] = "01SSP";
// write to the php log if the severity and subsystem match the filters currently set in the INI or
// the script (sqlsrv_configure).
void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...)
{
SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." );
va_list args;
va_start( args, msg );
g_driver_log( severity TSRMLS_CC, msg, &args );
va_end( args );
}
void core_sqlsrv_register_logger( log_callback driver_logger )
{
g_driver_log = driver_logger;
}
// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new
// length in the len parameter. If no errors occurred during convertion, true is returned and the original
// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed
// and false is returned.
bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len)
{
SQLSRV_ASSERT( string != NULL && *string != NULL, "String must be specified" );
// for the empty string, we simply returned we converted it
if( len == 0 && *string[0] == '\0' ) {
return true;
}
char* outString = NULL;
SQLINTEGER outLen = 0;
bool result = convert_string_from_utf16( encoding, reinterpret_cast<const wchar_t*>(*string), len / sizeof(wchar_t), &outString, outLen);
if (result)
{
sqlsrv_free( *string );
*string = outString;
len = outLen;
}
return result;
}
bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen )
{
SQLSRV_ASSERT( inString != NULL, "Input string must be specified" );
SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" );
SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" );
if (cchInLen == 0 && inString[0] == L'\0') {
*outString = reinterpret_cast<char*>( sqlsrv_malloc ( 1 ) );
*outString[0] = '\0';
cchOutLen = 0;
return true;
}
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
DWORD flags = 0;
if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
// calculate the number of characters needed
cchOutLen = WideCharToMultiByte( encoding, flags,
inString, cchInLen,
NULL, 0, NULL, NULL );
if( cchOutLen == 0 ) {
return false;
}
// Create a buffer to fit the encoded string
char* newString = reinterpret_cast<char*>( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ ));
int rc = WideCharToMultiByte( encoding, flags,
inString, cchInLen,
newString, cchOutLen, NULL, NULL );
if( rc == 0 ) {
cchOutLen = 0;
sqlsrv_free( newString );
return false;
}
*outString = newString;
newString[cchOutLen] = '\0'; // null terminate the encoded string
return true;
}
// thin wrapper around convert_string_from_default_encoding that handles
// allocation of the destination string. An empty string passed in returns
// failure since it's a failure case for convert_string_from_default_encoding.
wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len,
unsigned int* utf16_len )
{
*utf16_len = (mbcs_len + 1);
wchar_t* utf16_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( *utf16_len * sizeof( wchar_t )));
*utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len,
utf16_string, *utf16_len );
if( *utf16_len == 0 ) {
// we preserve the error and reset it because sqlsrv_free resets the last error
DWORD last_error = GetLastError();
sqlsrv_free( utf16_string );
SetLastError( last_error );
return NULL;
}
return utf16_string;
}
// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the
// errno is 1 based. It returns it as an array with 3 members:
// 1/SQLSTATE) sqlstate
// 2/code) driver specific error code
// 3/message) driver specific error message
// The fetch type determines if the indices are numeric, associative, or both.
bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity
TSRMLS_DC )
{
SQLHANDLE h = ctx.handle();
SQLSMALLINT h_type = ctx.handle_type();
if( h == NULL ) {
return false;
}
zval* ssphp_z = NULL;
int zr = SUCCESS;
zval* temp = NULL;
SQLRETURN r = SQL_SUCCESS;
SQLSMALLINT wmessage_len = 0;
SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ];
SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLSRV_ENCODING enc = ctx.encoding();
switch( h_type ) {
case SQL_HANDLE_STMT:
{
sqlsrv_stmt* stmt = static_cast<sqlsrv_stmt*>( &ctx );
if( stmt->current_results != NULL ) {
error = stmt->current_results->get_diag_rec( record_number );
// don't use the CHECK* macros here since it will trigger reentry into the error handling system
if( error == NULL ) {
return false;
}
break;
}
// convert the error into the encoding of the context
if( enc == SQLSRV_ENCODING_DEFAULT ) {
enc = stmt->conn->encoding();
}
}
default:
error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message,
SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len );
// don't use the CHECK* macros here since it will trigger reentry into the error handling system
if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
return false;
}
SQLINTEGER sqlstate_len = 0;
convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len);
SQLINTEGER message_len = 0;
convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len);
break;
}
// log the error first
LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate );
LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code );
LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message );
error->format = false;
return true;
}
// format and return a driver specfic error
void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error,
sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args )
{
// allocate space for the formatted message
formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
formatted_error->sqlstate = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
formatted_error->native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 ));
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast<LPSTR>( custom_error->native_message ), 0, 0,
reinterpret_cast<LPSTR>( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args );
if( rc == 0 ) {
strcpy_s( reinterpret_cast<char*>( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH,
reinterpret_cast<char*>( INTERNAL_FORMAT_ERROR ));
}
strcpy_s( reinterpret_cast<char*>( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE,
reinterpret_cast<char*>( custom_error->sqlstate ));
formatted_error->native_code = custom_error->native_code;
// log the error
LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate );
LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code );
LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message );
}
DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... )
{
va_list format_args;
va_start( format_args, format );
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args );
va_end( format_args );
return rc;
}
// return an error message for GetLastError using FormatMessage.
// this function returns the msg pointer so that it may be used within
// another function call such as handle_error
const char* get_last_error_message( DWORD last_error )
{
if( last_error == 0 ) {
last_error = GetLastError();
}
DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
last_err_msg, sizeof( last_err_msg ), NULL );
if( r == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg ));
std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg );
}
return last_err_msg;
}
// die
// Terminate the PHP request with an error message
// We use this function rather than php_error directly because we use the FormatMessage syntax in most other
// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were
// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without
// proper information. Rather than convert those messages and try and remember the difference between LOG and
// DIE, it is simpler to make the format syntax common between them.
void die( const char* msg, ... )
{
va_list format_args;
va_start( format_args, msg );
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args );
va_end( format_args );
if( rc == 0 ) {
php_error( E_ERROR, reinterpret_cast<const char*>( INTERNAL_FORMAT_ERROR ));
}
php_error( E_ERROR, last_err_msg );
}
namespace {
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string. An empty string passed in will result as
// a failure since MBTWC returns 0 for both an empty string and failure
// to convert.
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string,
unsigned int mbcs_len, __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string,
unsigned int utf16_len )
{
unsigned int win_encoding = CP_ACP;
switch( php_encoding ) {
case SQLSRV_ENCODING_CHAR:
win_encoding = CP_ACP;
break;
// this shouldn't ever be set
case SQLSRV_ENCODING_BINARY:
DIE( "Invalid encoding." );
break;
default:
win_encoding = php_encoding;
break;
}
unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len,
utf16_out_string, utf16_len );
if( required_len == 0 ) {
return 0;
}
utf16_out_string[ required_len ] = '\0';
return required_len;
}
}

View file

@ -1,2343 +0,0 @@
//-----------------------------------------------------------------------------
// File: msodbcsql.h
//
// Copyright: Copyright (c) Microsoft Corporation
//
// Contents: ODBC driver for SQL Server specific definitions.
//
//-----------------------------------------------------------------------------
#ifndef __msodbcsql_h__
#define __msodbcsql_h__
#if !defined(SQLODBC_VER)
#define SQLODBC_VER 1100
#endif
#if SQLODBC_VER >= 1100
#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server"
#define SQLODBC_FILE_NAME_ANSI "msodbcsql"
#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11"
#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll"
#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server"
#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql"
#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11"
#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll"
// define the character type agnostic constants
#if defined(_UNICODE) || defined(UNICODE)
#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE
#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE
#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE
#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE
#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE
#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE
#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE
#else // _UNICODE || UNICODE
#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI
#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI
#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI
#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI
#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI
#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI
#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI
#endif // _UNICODE || UNICODE
#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER
#endif // SQLODBC_VER
#ifndef __sqlncli_h__
#if !defined(SQLNCLI_VER)
#define SQLNCLI_VER 1100
#endif
#if SQLNCLI_VER >= 1100
#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client"
#define SQLNCLI_FILE_NAME_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11"
#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll"
#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client"
#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11"
#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll"
#elif SQLNCLI_VER >= 1000
#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client"
#define SQLNCLI_FILE_NAME_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10"
#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll"
#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client"
#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10"
#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll"
#else
#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client"
#define SQLNCLI_FILE_NAME_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll"
#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client"
#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll"
#endif // SQLNCLI_VER >= 1100
// define the character type agnostic constants
#if defined(_UNICODE) || defined(UNICODE)
#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE
#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE
#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE
#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE
#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE
#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE
#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE
#else // _UNICODE || UNICODE
#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI
#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI
#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI
#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI
#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI
#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI
#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI
#endif // _UNICODE || UNICODE
#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER
#ifdef ODBCVER
#ifdef __cplusplus
extern "C" {
#endif
// max SQL Server identifier length
#define SQL_MAX_SQLSERVERNAME 128
// SQLSetConnectAttr driver specific defines.
// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage.
// Connection attributes
#define SQL_COPT_SS_BASE 1200
#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only
#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare
#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login
#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact
#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata
#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction
#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction
#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections
#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data
#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data
#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds.
#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies.
#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging.
#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk.
#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers
#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings
#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection
#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation
#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database
#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL
#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data
#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection
#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server
#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login
#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level
#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate
#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN
#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN
#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection
#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated
#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID
// Define old names
#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD
#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP
#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY
#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS
// SQLSetStmtAttr SQL Server Native Client driver specific defines.
// Statement attributes
#define SQL_SOPT_SS_BASE 1225
#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging
#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only
#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns
#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option
#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions
#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options
#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator
#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary
#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout
#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text
#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name
#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types
#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls
#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE
// Define old names
#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING
#define SQL_COPT_SS_BASE_EX 1240
#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation
#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request.
#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion.
#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check
#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K)
#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet
#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent
#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover
#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER
// SQLColAttributes driver specific defines.
// SQLSetDescField/SQLGetDescField driver specific defines.
// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_CA_SS_BASE 1200
#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype
#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype
#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders
#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol
#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen
#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute
#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return
#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist
#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid
#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop
#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen
#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE)
#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE)
//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location.
#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars)
#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15)
#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16)
#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17)
// XML, CLR UDT, and table valued parameter related metadata
#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name
#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name
#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name
#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class
#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection
#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection
#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection
#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name
#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name
#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name
// table valued parameter related metadata
#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed
#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key
#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order
#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal
#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter
// sparse column related metadata
#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns
// Legacy datetime related metadata
#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types
#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36)
// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE
#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option
// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP
#define SQL_UP_OFF 0L // Procedures won't be used for prepare
#define SQL_UP_ON 1L // Procedures will be used for prepare
#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped
#define SQL_UP_DEFAULT SQL_UP_ON
// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only
#define SQL_IS_OFF 0L // Integrated security isn't used
#define SQL_IS_ON 1L // Integrated security is used
#define SQL_IS_DEFAULT SQL_IS_OFF
// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS
#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact
#define SQL_PC_ON 1L // Cursors remain open on SQLTransact
#define SQL_PC_DEFAULT SQL_PC_OFF
// Defines for use with SQL_COPT_SS_USER_DATA
#define SQL_UD_NOTSET NULL // No user data pointer set
// Defines for use with SQL_COPT_SS_TRANSLATE
#define SQL_XL_OFF 0L // Code page translation is not performed
#define SQL_XL_ON 1L // Code page translation is performed
#define SQL_XL_DEFAULT SQL_XL_ON
// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only
#define SQL_FB_OFF 0L // FallBack connections are disabled
#define SQL_FB_ON 1L // FallBack connections are enabled
#define SQL_FB_DEFAULT SQL_FB_OFF
// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only
#define SQL_BCP_OFF 0L // BCP is not allowed on connection
#define SQL_BCP_ON 1L // BCP is allowed on connection
#define SQL_BCP_DEFAULT SQL_BCP_OFF
// Defines for use with SQL_COPT_SS_QUOTED_IDENT
#define SQL_QI_OFF 0L // Quoted identifiers are enable
#define SQL_QI_ON 1L // Quoted identifiers are disabled
#define SQL_QI_DEFAULT SQL_QI_ON
// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only
#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled
#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled
#define SQL_AD_DEFAULT SQL_AD_ON
// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only
#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off
#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on
#define SQL_CN_DEFAULT SQL_CN_ON
// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING
#define SQL_TL_OFF 0L // No logging on text pointer ops
#define SQL_TL_ON 1L // Logging occurs on text pointer ops
#define SQL_TL_DEFAULT SQL_TL_ON
// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS
#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden
#define SQL_HC_ON 1L // FOR BROWSE columns are exposed
#define SQL_HC_DEFAULT SQL_HC_OFF
// Defines for use with SQL_SOPT_SS_NOBROWSETABLE
#define SQL_NB_OFF 0L // NO_BROWSETABLE is off
#define SQL_NB_ON 1L // NO_BROWSETABLE is on
#define SQL_NB_DEFAULT SQL_NB_OFF
// Defines for use with SQL_SOPT_SS_REGIONALIZE
#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions
#define SQL_RE_ON 1L // Regionalization occurs on output character conversions
#define SQL_RE_DEFAULT SQL_RE_OFF
// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS
#define SQL_CO_OFF 0L // Clear all cursor options
#define SQL_CO_FFO 1L // Fast-forward cursor will be used
#define SQL_CO_AF 2L // Autofetch on cursor open
#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch
#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors
#define SQL_CO_DEFAULT SQL_CO_OFF
//SQL_SOPT_SS_NOCOUNT_STATUS
#define SQL_NC_OFF 0L
#define SQL_NC_ON 1L
//SQL_SOPT_SS_DEFER_PREPARE
#define SQL_DP_OFF 0L
#define SQL_DP_ON 1L
//SQL_SOPT_SS_NAME_SCOPE
#define SQL_SS_NAME_SCOPE_TABLE 0L
#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L
#define SQL_SS_NAME_SCOPE_EXTENDED 2L
#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L
#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE
//SQL_COPT_SS_ENCRYPT
#define SQL_EN_OFF 0L
#define SQL_EN_ON 1L
//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE
#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L
#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L
//SQL_COPT_SS_BROWSE_CONNECT
#define SQL_MORE_INFO_NO 0L
#define SQL_MORE_INFO_YES 1L
//SQL_COPT_SS_BROWSE_CACHE_DATA
#define SQL_CACHE_DATA_NO 0L
#define SQL_CACHE_DATA_YES 1L
//SQL_COPT_SS_RESET_CONNECTION
#define SQL_RESET_YES 1L
//SQL_COPT_SS_WARN_ON_CP_ERROR
#define SQL_WARN_NO 0L
#define SQL_WARN_YES 1L
//SQL_COPT_SS_MARS_ENABLED
#define SQL_MARS_ENABLED_NO 0L
#define SQL_MARS_ENABLED_YES 1L
/* SQL_TXN_ISOLATION_OPTION bitmasks */
#define SQL_TXN_SS_SNAPSHOT 0x00000020L
// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER
#define SQL_SS_ORDER_UNSPECIFIED 0L
#define SQL_SS_DESCENDING_ORDER 1L
#define SQL_SS_ASCENDING_ORDER 2L
#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED
// Driver specific SQL data type defines.
// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_SS_VARIANT (-150)
#define SQL_SS_UDT (-151)
#define SQL_SS_XML (-152)
#define SQL_SS_TABLE (-153)
#define SQL_SS_TIME2 (-154)
#define SQL_SS_TIMESTAMPOFFSET (-155)
// Local types to be used with SQL_CA_SS_SERVER_TYPE
#define SQL_SS_TYPE_DEFAULT 0L
#define SQL_SS_TYPE_SMALLDATETIME 1L
#define SQL_SS_TYPE_DATETIME 2L
// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager.
#define SQL_C_TYPES_EXTENDED 0x04000L
#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0)
#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1)
#ifndef SQLNCLI_NO_BCP
// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application
// and you want to exclude the BCP-related definitions in this header file.
// SQL Server Data Type defines.
// New types for SQL 6.0 and later servers
#define SQLTEXT 0x23
#define SQLVARBINARY 0x25
#define SQLINTN 0x26
#define SQLVARCHAR 0x27
#define SQLBINARY 0x2d
#define SQLIMAGE 0x22
#define SQLCHARACTER 0x2f
#define SQLINT1 0x30
#define SQLBIT 0x32
#define SQLINT2 0x34
#define SQLINT4 0x38
#define SQLMONEY 0x3c
#define SQLDATETIME 0x3d
#define SQLFLT8 0x3e
#define SQLFLTN 0x6d
#define SQLMONEYN 0x6e
#define SQLDATETIMN 0x6f
#define SQLFLT4 0x3b
#define SQLMONEY4 0x7a
#define SQLDATETIM4 0x3a
// New types for SQL 6.0 and later servers
#define SQLDECIMAL 0x6a
#define SQLNUMERIC 0x6c
// New types for SQL 7.0 and later servers
#define SQLUNIQUEID 0x24
#define SQLBIGCHAR 0xaf
#define SQLBIGVARCHAR 0xa7
#define SQLBIGBINARY 0xad
#define SQLBIGVARBINARY 0xa5
#define SQLBITN 0x68
#define SQLNCHAR 0xef
#define SQLNVARCHAR 0xe7
#define SQLNTEXT 0x63
// New types for SQL 2000 and later servers
#define SQLINT8 0x7f
#define SQLVARIANT 0x62
// New types for SQL 2005 and later servers
#define SQLUDT 0xf0
#define SQLXML 0xf1
// New types for SQL 2008 and later servers
#define SQLTABLE 0xf3
#define SQLDATEN 0x28
#define SQLTIMEN 0x29
#define SQLDATETIME2N 0x2a
#define SQLDATETIMEOFFSETN 0x2b
// Define old names
#define SQLDECIMALN 0x6a
#define SQLNUMERICN 0x6c
#endif // SQLNCLI_NO_BCP
// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of
// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns
#define SQL_SS_LENGTH_UNLIMITED 0
// User Data Type definitions.
// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE.
#define SQLudtBINARY 3
#define SQLudtBIT 16
#define SQLudtBITN 0
#define SQLudtCHAR 1
#define SQLudtDATETIM4 22
#define SQLudtDATETIME 12
#define SQLudtDATETIMN 15
#define SQLudtDECML 24
#define SQLudtDECMLN 26
#define SQLudtFLT4 23
#define SQLudtFLT8 8
#define SQLudtFLTN 14
#define SQLudtIMAGE 20
#define SQLudtINT1 5
#define SQLudtINT2 6
#define SQLudtINT4 7
#define SQLudtINTN 13
#define SQLudtMONEY 11
#define SQLudtMONEY4 21
#define SQLudtMONEYN 17
#define SQLudtNUM 10
#define SQLudtNUMN 25
#define SQLudtSYSNAME 18
#define SQLudtTEXT 19
#define SQLudtTIMESTAMP 80
#define SQLudtUNIQUEIDENTIFIER 0
#define SQLudtVARBINARY 4
#define SQLudtVARCHAR 2
#define MIN_USER_DATATYPE 256
// Aggregate operator types.
// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP.
#define SQLAOPSTDEV 0x30 // Standard deviation
#define SQLAOPSTDEVP 0x31 // Standard deviation population
#define SQLAOPVAR 0x32 // Variance
#define SQLAOPVARP 0x33 // Variance population
#define SQLAOPCNT 0x4b // Count
#define SQLAOPSUM 0x4d // Sum
#define SQLAOPAVG 0x4f // Average
#define SQLAOPMIN 0x51 // Min
#define SQLAOPMAX 0x52 // Max
#define SQLAOPANY 0x53 // Any
#define SQLAOPNOOP 0x56 // None
// SQLGetInfo driver specific defines.
// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_INFO_SS_FIRST 1199
#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo
#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo
#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA
#ifdef UNICODE
#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW
#else
#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA
#endif
// SQLGetDiagField driver specific defines.
// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_DIAG_SS_BASE (-1150)
#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE)
#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1)
#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2)
#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3)
#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4)
// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines.
// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_DIAG_DFC_SS_BASE (-200)
#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0)
#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1)
#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2)
#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3)
#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4)
#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5)
#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6)
#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7)
#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8)
#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9)
#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10)
#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11)
#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12)
#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13)
#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14)
#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15)
#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16)
#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17)
#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18)
#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19)
#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20)
#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20)
#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21)
#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22)
#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22)
#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23)
#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24)
#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25)
#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26)
#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26)
#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27)
#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27)
#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28)
#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29)
#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29)
#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30)
#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31)
#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32)
#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33)
#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34)
#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35)
#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36)
#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37)
#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38)
#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39)
#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40)
#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41)
#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42)
#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43)
#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44)
#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45)
#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46)
#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47)
#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48)
#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49)
#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50)
#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51)
#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52)
#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53)
#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54)
#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55)
#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56)
// Severity codes for SQL_DIAG_SS_SEVERITY
#define EX_ANY 0
#define EX_INFO 10
#define EX_MAXISEVERITY EX_INFO
#define EX_MISSING 11
#define EX_TYPE 12
#define EX_DEADLOCK 13
#define EX_PERMIT 14
#define EX_SYNTAX 15
#define EX_USER 16
#define EX_RESOURCE 17
#define EX_INTOK 18
#define MAXUSEVERITY EX_INTOK
#define EX_LIMIT 19
#define EX_CMDFATAL 20
#define MINFATALERR EX_CMDFATAL
#define EX_DBFATAL 21
#define EX_TABCORRUPT 22
#define EX_DBCORRUPT 23
#define EX_HARDWARE 24
#define EX_CONTROL 25
// Internal server datatypes - used when binding to SQL_C_BINARY
#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts
// DB-Library datatypes
#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte
#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte
#ifdef UNICODE
typedef wchar_t DBCHAR;
#else
typedef char DBCHAR;
#endif
typedef short SQLSMALLINT;
typedef unsigned short SQLUSMALLINT;
typedef unsigned char DBBINARY;
typedef unsigned char DBTINYINT;
typedef short DBSMALLINT;
typedef unsigned short DBUSMALLINT;
typedef double DBFLT8;
typedef unsigned char DBBIT;
typedef unsigned char DBBOOL;
typedef float DBFLT4;
typedef DBFLT4 DBREAL;
typedef UINT DBUBOOL;
typedef struct dbmoney
{
LONG mnyhigh;
ULONG mnylow;
} DBMONEY;
typedef struct dbdatetime
{
LONG dtdays;
ULONG dttime;
} DBDATETIME;
typedef struct dbdatetime4
{
USHORT numdays;
USHORT nummins;
} DBDATETIM4;
typedef LONG DBMONEY4;
#include <pshpack8.h> // 8-byte structure packing
// New Date Time Structures
// New Structure for TIME2
typedef struct tagSS_TIME2_STRUCT
{
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
} SQL_SS_TIME2_STRUCT;
// New Structure for TIMESTAMPOFFSET
typedef struct tagSS_TIMESTAMPOFFSET_STRUCT
{
SQLSMALLINT year;
SQLUSMALLINT month;
SQLUSMALLINT day;
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
SQLSMALLINT timezone_hour;
SQLSMALLINT timezone_minute;
} SQL_SS_TIMESTAMPOFFSET_STRUCT;
typedef struct tagDBTIME2
{
USHORT hour;
USHORT minute;
USHORT second;
ULONG fraction;
} DBTIME2;
typedef struct tagDBTIMESTAMPOFFSET
{
SHORT year;
USHORT month;
USHORT day;
USHORT hour;
USHORT minute;
USHORT second;
ULONG fraction;
SHORT timezone_hour;
SHORT timezone_minute;
} DBTIMESTAMPOFFSET;
#include <poppack.h> // restore original structure packing
// Money value *10,000
#define DBNUM_PREC_TYPE BYTE
#define DBNUM_SCALE_TYPE BYTE
#define DBNUM_VAL_TYPE BYTE
#if (ODBCVER < 0x0300)
#define MAXNUMERICLEN 16
typedef struct dbnumeric // Internal representation of NUMERIC data type
{
DBNUM_PREC_TYPE precision; // Precision
DBNUM_SCALE_TYPE scale; // Scale
BYTE sign; // Sign (1 if positive, 0 if negative)
DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value
} DBNUMERIC;
typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type
#else // Use ODBC 3.0 definitions since same as DBLib
#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN
typedef SQL_NUMERIC_STRUCT DBNUMERIC;
typedef SQL_NUMERIC_STRUCT DBDECIMAL;
#endif // ODCBVER
#endif // MAXNUMERICLEN
#ifndef INT
typedef int INT;
typedef LONG DBINT;
typedef DBINT * LPDBINT;
#ifndef _LPCBYTE_DEFINED
#define _LPCBYTE_DEFINED
typedef BYTE const* LPCBYTE;
#endif //_LPCBYTE_DEFINED
#endif // INT
/**************************************************************************
This struct is a global used for gathering statistical data on the driver.
Access to this structure is controlled via the pStatCrit;
***************************************************************************/
typedef struct sqlperf
{
// Application Profile Statistics
DWORD TimerResolution;
DWORD SQLidu;
DWORD SQLiduRows;
DWORD SQLSelects;
DWORD SQLSelectRows;
DWORD Transactions;
DWORD SQLPrepares;
DWORD ExecDirects;
DWORD SQLExecutes;
DWORD CursorOpens;
DWORD CursorSize;
DWORD CursorUsed;
LDOUBLE PercentCursorUsed;
LDOUBLE AvgFetchTime;
LDOUBLE AvgCursorSize;
LDOUBLE AvgCursorUsed;
DWORD SQLFetchTime;
DWORD SQLFetchCount;
DWORD CurrentStmtCount;
DWORD MaxOpenStmt;
DWORD SumOpenStmt;
// Connection Statistics
DWORD CurrentConnectionCount;
DWORD MaxConnectionsOpened;
DWORD SumConnectionsOpened;
DWORD SumConnectiontime;
LDOUBLE AvgTimeOpened;
// Network Statistics
DWORD ServerRndTrips;
DWORD BuffersSent;
DWORD BuffersRec;
DWORD BytesSent;
DWORD BytesRec;
// Time Statistics;
DWORD msExecutionTime;
DWORD msNetWorkServerTime;
} SQLPERF;
// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY
#define SQL_PERF_START 1 // Starts the driver sampling performance data.
#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data.
// The following are defines for SQL_COPT_SS_PERF_DATA_LOG
#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG")
// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG
#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG")
// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL
#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds
#ifndef SQLNCLI_NO_BCP
// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application
// and you want to exclude the BCP-related definitions in this header file.
// ODBC BCP prototypes and defines
// Return codes
#define SUCCEED 1
#define FAIL 0
#define SUCCEED_ABORT 2
#define SUCCEED_ASYNC 3
// Transfer directions
#define DB_IN 1 // Transfer from client to server
#define DB_OUT 2 // Transfer from server to client
// bcp_control option
#define BCPMAXERRS 1 // Sets max errors allowed
#define BCPFIRST 2 // Sets first row to be copied out
#define BCPLAST 3 // Sets number of rows to be copied out
#define BCPBATCH 4 // Sets input batch size
#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values
#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT
#define BCPODBC 7 // Sets ODBC canonical character output
#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on
#if SQLNCLI_VER < 1000
#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on
#endif
#define BCPHINTSA 10 // Sets server BCP hints (ANSI string)
#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string)
#define BCPFILECP 12 // Sets clients code page for the file
#define BCPUNICODEFILE 13 // Sets that the file contains unicode header
#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically
#define BCPFILEFMT 15 // Sets file format version
#define BCPFMTXML 16 // Sets the format file type to xml
#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit)
#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit)
#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit)
#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec
// BCPFILECP values
// Any valid code page that is installed on the client can be passed plus:
#define BCPFILECP_ACP 0 // Data in file is in Windows code page
#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default)
#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion)
// bcp_collen definition
#define SQL_VARLEN_DATA (-10) // Use default length for column
// BCP column format properties
#define BCP_FMT_TYPE 0x01
#define BCP_FMT_INDICATOR_LEN 0x02
#define BCP_FMT_DATA_LEN 0x03
#define BCP_FMT_TERMINATOR 0x04
#define BCP_FMT_SERVER_COL 0x05
#define BCP_FMT_COLLATION 0x06
#define BCP_FMT_COLLATION_ID 0x07
// bcp_setbulkmode properties
#define BCP_OUT_CHARACTER_MODE 0x01
#define BCP_OUT_WIDE_CHARACTER_MODE 0x02
#define BCP_OUT_NATIVE_TEXT_MODE 0x03
#define BCP_OUT_NATIVE_MODE 0x04
// BCP functions
DBINT SQL_API bcp_batch (HDBC);
RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT);
RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT);
RETCODE SQL_API bcp_collen (HDBC, DBINT, INT);
RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT);
RETCODE SQL_API bcp_columns (HDBC, INT);
RETCODE SQL_API bcp_control (HDBC, INT, void *);
DBINT SQL_API bcp_done (HDBC);
RETCODE SQL_API bcp_exec (HDBC, LPDBINT);
RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *);
RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT);
RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT);
RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE);
RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR);
RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR);
RETCODE SQL_API bcp_sendrow (HDBC);
RETCODE SQL_API bcp_setbulkmode (HDBC, INT, __in_bcount(cbField) void*, INT cbField, __in_bcount(cbRow) void *, INT cbRow);
RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT);
RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR);
RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR);
CHAR* SQL_API dbprtypeA (INT);
WCHAR* SQL_API dbprtypeW (INT);
CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL);
WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL);
#ifdef UNICODE
#define bcp_init bcp_initW
#define bcp_readfmt bcp_readfmtW
#define bcp_writefmt bcp_writefmtW
#define dbprtype dbprtypeW
#define bcp_gettypename bcp_gettypenameW
#define BCPHINTS BCPHINTSW
#else
#define bcp_init bcp_initA
#define bcp_readfmt bcp_readfmtA
#define bcp_writefmt bcp_writefmtA
#define dbprtype dbprtypeA
#define bcp_gettypename bcp_gettypenameA
#define BCPHINTS BCPHINTSA
#endif // UNICODE
#endif // SQLNCLI_NO_BCP
// The following options have been deprecated
#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0)
// Defines for use with SQL_FAST_CONNECT - only useable before connecting
#define SQL_FC_OFF 0L // Fast connect is off
#define SQL_FC_ON 1L // Fast connect is on
#define SQL_FC_DEFAULT SQL_FC_OFF
#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6)
#define SQL_AO_OFF 0L
#define SQL_AO_ON 1L
#define SQL_AO_DEFAULT SQL_AO_OFF
#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME
#ifdef __cplusplus
} // extern "C"
#endif
#endif // ODBCVER
#ifdef __cplusplus
extern "C" {
#endif
#include <windows.h>
//The following facilitates opening a handle to a SQL filestream
typedef enum _SQL_FILESTREAM_DESIRED_ACCESS {
SQL_FILESTREAM_READ = 0,
SQL_FILESTREAM_WRITE = 1,
SQL_FILESTREAM_READWRITE = 2
} SQL_FILESTREAM_DESIRED_ACCESS;
#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L
#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L
#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L
#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L
#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L
HANDLE __stdcall OpenSqlFilestream (
LPCWSTR FilestreamPath,
SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess,
ULONG OpenOptions,
__in_bcount(FilestreamTransactionContextLength)
LPBYTE FilestreamTransactionContext,
SSIZE_T FilestreamTransactionContextLength,
PLARGE_INTEGER AllocationSize);
#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS)
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__sqlncli_h__
#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount
#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval
#ifdef SQL_COPT_SS_MAX_USED
#undef SQL_COPT_SS_MAX_USED
#endif // SQL_COPT_SS_MAX_USED
#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL
#ifndef _SQLUSERINSTANCE_H_
#define _SQLUSERINSTANCE_H_
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
// Recommended buffer size to store a LocalDB connection string
#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260
// type definition for LocalDBCreateInstance function
typedef HRESULT __cdecl FnLocalDBCreateInstance (
// I the LocalDB version (e.g. 11.0 or 11.0.1094.2)
__in_z PCWSTR wszVersion,
// I the instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
);
// type definition for pointer to LocalDBCreateInstance function
typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance;
// type definition for LocalDBStartInstance function
typedef HRESULT __cdecl FnLocalDBStartInstance (
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags,
// O the buffer to store the connection string to the LocalDB instance
__out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection,
// I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null.
__inout_opt LPDWORD lpcchSqlConnection
);
// type definition for pointer to LocalDBStartInstance function
typedef FnLocalDBStartInstance* PFnLocalDBStartInstance;
// Flags for the LocalDBFormatMessage function
#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L
// type definition for LocalDBFormatMessage function
typedef HRESULT __cdecl FnLocalDBFormatMessage(
// I the LocalDB error code
__in HRESULT hrLocalDB,
// I Available flags:
// LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short,
// the error message will be truncated to fit into the buffer
__in DWORD dwFlags,
// I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used)
__in DWORD dwLanguageId,
// O the buffer to store the LocalDB error message
__out_ecount_z(*lpcchMessage) LPWSTR wszMessage,
// I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null. If the function succeeds
// contains the number of characters in the message, excluding the trailing null
__inout LPDWORD lpcchMessage
);
// type definition for function pointer to LocalDBFormatMessage function
typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage;
// MessageId: LOCALDB_ERROR_NOT_INSTALLED
//
// MessageText:
//
// LocalDB is not installed.
//
#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L)
//---------------------------------------------------------------------
// Function: LocalDBCreateInstance
//
// Description: This function will create the new LocalDB instance.
//
// Available Flags:
// No flags available. Reserved for future use.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance
// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH
// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed
// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty
// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version
// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile%
// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified
// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created
// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed.
// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted
//
FnLocalDBCreateInstance LocalDBCreateInstance;
//---------------------------------------------------------------------
// Function: LocalDBStartInstance
//
// Description: This function will start the given LocalDB instance.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small
// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH
// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified
// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created
// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed.
// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted
//
FnLocalDBStartInstance LocalDBStartInstance;
// type definition for LocalDBStopInstance function
typedef HRESULT __cdecl FnLocalDBStopInstance (
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I Available flags:
// LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately
// LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option
__in DWORD dwFlags,
// I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately
// without waiting for LocalDB instance to stop
__in ULONG ulTimeout
);
// type definition for pointer to LocalDBStopInstance function
typedef FnLocalDBStopInstance* PFnLocalDBStopInstance;
// Flags for the StopLocalDBInstance function
#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L
#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L
//---------------------------------------------------------------------
// Function: LocalDBStopInstance
//
// Description: This function will shutdown the given LocalDB instance.
// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately.
// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBStopInstance LocalDBStopInstance;
// type definition for LocalDBDeleteInstance function
typedef HRESULT __cdecl FnLocalDBDeleteInstance (
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
);
// type definition for pointer to LocalDBDeleteInstance function
typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance;
//---------------------------------------------------------------------
// Function: LocalDBDeleteInstance
//
// Description: This function will remove the given LocalDB instance. If the given instance is running this function will
// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist
// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBDeleteInstance LocalDBDeleteInstance;
// Function: LocalDBFormatMessage
//
// Description: This function will return the localized textual description for the given LocalDB error
//
// Available Flags:
// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer
//
// Return Value:
// S_OK, if the function succeeds
//
// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown
// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language)
// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag
// is not set
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBFormatMessage LocalDBFormatMessage;
#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128
#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME
typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1];
typedef TLocalDBInstanceName* PTLocalDBInstanceName;
// type definition for LocalDBGetInstances function
typedef HRESULT __cdecl FnLocalDBGetInstances(
// O buffer for a LocalDB instance names
__out PTLocalDBInstanceName pInstanceNames,
// I/O on input has the number slots for instance names in the pInstanceNames buffer. On output,
// has the number of existing LocalDB instances
__inout LPDWORD lpdwNumberOfInstances
);
// type definition for pointer to LocalDBGetInstances function
typedef FnLocalDBGetInstances* PFnLocalDBGetInstances;
// Function: LocalDBGetInstances
//
// Description: This function returns names for all existing Local DB instances
//
// Usage Example:
// DWORD dwN = 0;
// LocalDBGetInstances(NULL, &dwN);
// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName));
// LocalDBGetInstances(insts, &dwN);
// for (int i = 0; i < dwN; i++)
// wprintf(L"%s\n", insts[i]);
//
// Return values:
// S_OK, if the function succeeds
//
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBGetInstances LocalDBGetInstances;
// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15
#define MAX_STRING_SID_LENGTH 186
#pragma pack(push)
#pragma pack(8)
// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file.
//
typedef struct _LocalDBInstanceInfo
{
DWORD cbLocalDBInstanceInfoSize;
TLocalDBInstanceName wszInstanceName;
BOOL bExists;
BOOL bConfigurationCorrupted;
BOOL bIsRunning;
DWORD dwMajor;
DWORD dwMinor;
DWORD dwBuild;
DWORD dwRevision;
FILETIME ftLastStartDateUTC;
WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE];
BOOL bIsShared;
TLocalDBInstanceName wszSharedInstanceName;
WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1];
BOOL bIsAutomatic;
} LocalDBInstanceInfo;
#pragma pack(pop)
typedef LocalDBInstanceInfo* PLocalDBInstanceInfo;
// type definition for LocalDBGetInstanceInfo function
typedef HRESULT __cdecl FnLocalDBGetInstanceInfo(
// I the LocalDB instance name
__in_z PCWSTR wszInstanceName,
// O instance information
__out PLocalDBInstanceInfo pInfo,
// I Size of LocalDBInstanceInfo structure in bytes
__in DWORD cbInfo);
// type definition for pointer to LocalDBGetInstances function
typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo;
// Function: LocalDBGetInstanceInfo
//
// Description: This function returns information about the given instance.
//
// Return values:
// S_OK, if the function succeeds
//
// ERROR_INVALID_PARAMETER, if some of the parameters is invalid
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo;
// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots
//
#define MAX_LOCALDB_VERSION_LENGTH 43
typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1];
typedef TLocalDBVersion* PTLocalDBVersion;
// type definition for LocalDBGetVersions function
typedef HRESULT __cdecl FnLocalDBGetVersions(
// O buffer for installed LocalDB versions
__out PTLocalDBVersion pVersions,
// I/O on input has the number slots for versions in the pVersions buffer. On output,
// has the number of existing LocalDB versions
__inout LPDWORD lpdwNumberOfVersions
);
// type definition for pointer to LocalDBGetVersions function
typedef FnLocalDBGetVersions* PFnLocalDBGetVersions;
// Function: LocalDBGetVersions
//
// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor
//
// Usage Example:
// DWORD dwN = 0;
// LocalDBGetVersions(NULL, &dwN);
// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion));
// LocalDBGetVersions(insts, &dwN);
// for (int i = 0; i < dwN; i++)
// wprintf(L"%s\n", insts[i]);
//
// Return values:
// S_OK, if the function succeeds
//
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs.
//
FnLocalDBGetVersions LocalDBGetVersions;
#pragma pack(push)
#pragma pack(8)
// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file.
//
typedef struct _LocalDBVersionInfo
{
DWORD cbLocalDBVersionInfoSize;
TLocalDBVersion wszVersion;
BOOL bExists;
DWORD dwMajor;
DWORD dwMinor;
DWORD dwBuild;
DWORD dwRevision;
} LocalDBVersionInfo;
#pragma pack(pop)
typedef LocalDBVersionInfo* PLocalDBVersionInfo;
// type definition for LocalDBGetVersionInfo function
typedef HRESULT __cdecl FnLocalDBGetVersionInfo(
// I LocalDB version string
__in_z PCWSTR wszVersion,
// O version information
__out PLocalDBVersionInfo pVersionInfo,
// I Size of LocalDBVersionInfo structure in bytes
__in DWORD cbVersionInfo
);
// type definition for pointer to LocalDBGetVersionInfo function
typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo;
// Function: LocalDBGetVersionInfo
//
// Description: This function returns information about the given LocalDB version
//
// Return values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred
// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid
//
FnLocalDBGetVersionInfo LocalDBGetVersionInfo;
typedef HRESULT __cdecl FnLocalDBStartTracing();
typedef FnLocalDBStartTracing* PFnLocalDBStartTracing;
// Function: LocalDBStartTracing
//
// Description: This function will write in registry that Tracing sessions should be started for the current user.
//
// Return values:
// S_OK - on success
// Propper HRESULT in case of failure
//
FnLocalDBStartTracing LocalDBStartTracing;
typedef HRESULT __cdecl FnLocalDBStopTracing();
typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing;
// Function: LocalDBStopTracing
//
// Description: This function will write in registry that Tracing sessions should be stopped for the current user.
//
// Return values:
// S_OK - on success
// Propper HRESULT in case of failure
//
FnLocalDBStopTracing LocalDBStopTracing;
// type definition for LocalDBShareInstance function
typedef HRESULT __cdecl FnLocalDBShareInstance(
// I the SID of the LocalDB instance owner
__in_opt PSID pOwnerSID,
// I the private name of LocalDB instance which should be shared
__in_z PCWSTR wszPrivateLocalDBInstanceName,
// I the public shared name
__in_z PCWSTR wszSharedName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags);
// type definition for pointer to LocalDBShareInstance function
typedef FnLocalDBShareInstance* PFnLocalDBShareInstance;
// Function: LocalDBShareInstance
//
// Description: This function will share the given private instance of the given user with the given shared name.
// This function has to be executed elevated.
//
// Return values:
// HRESULT
//
FnLocalDBShareInstance LocalDBShareInstance;
// type definition for LocalDBUnshareInstance function
typedef HRESULT __cdecl FnLocalDBUnshareInstance(
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags);
// type definition for pointer to LocalDBUnshareInstance function
typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance;
// Function: LocalDBUnshareInstance
//
// Description: This function unshares the given LocalDB instance.
// If a shared name is given then that shared instance will be unshared.
// If a private name is given then we will check if the caller
// shares a private instance with the given private name and unshare it.
//
// Return values:
// HRESULT
//
FnLocalDBUnshareInstance LocalDBUnshareInstance;
#ifdef __cplusplus
} // extern "C"
#endif
#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS)
//---------------------------------------------------------------------
// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS
// is defined. It provides an implementation of proxies for each of the LocalDB APIs.
// The proxy implementations use a common function to bind to entry points in the
// latest installed SqlUserInstance DLL, and then forward the requests.
//
// The current implementation loads the SqlUserInstance DLL on the first call into
// a proxy function. There is no provision for unloading the DLL. Note that if the
// process includes multiple binaries (EXE and one or more DLLs), each of them could
// load a separate instance of the SqlUserInstance DLL.
//
// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically.
//
// WARNING: these functions must not be called in DLL initialization, since a deadlock
// could result loading dependent DLLs.
//---------------------------------------------------------------------
// This macro provides the body for each proxy function.
//
#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn)
// Structure and function to parse the "Installed Versions" registry subkeys
//
typedef struct {
DWORD dwComponent[2];
WCHAR wszKeyName[256];
} Version;
// The following algorithm is intended to match, in part, the .NET Version class.
// A maximum of two components are allowed, which must be separated with a period.
// Valid: "11", "11.0"
// Invalid: "", ".0", "11.", "11.0."
//
static BOOL ParseVersion(Version * pVersion)
{
pVersion->dwComponent[0] = 0;
pVersion->dwComponent[1] = 0;
WCHAR * pwch = pVersion->wszKeyName;
for (int i = 0; i<2; i++)
{
LONGLONG llVal = 0;
BOOL fHaveDigit = FALSE;
while (*pwch >= L'0' && *pwch <= L'9')
{
llVal = llVal * 10 + (*pwch++ - L'0');
fHaveDigit = TRUE;
if (llVal > 0x7fffffff)
{
return FALSE;
}
}
if (!fHaveDigit)
return FALSE;
pVersion->dwComponent[i] = (DWORD) llVal;
if (*pwch == L'\0')
return TRUE;
if (*pwch != L'.')
return FALSE;
pwch++;
}
// If we get here, the version string was terminated with L'.', which is not valid
//
return FALSE;
}
#include <assert.h>
// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure.
// Note that the first-loaded API DLL for the process will be used until process termination: installation of
// a new version of the API will not be recognized after first load.
//
static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn)
{
static volatile HMODULE hLocalDBDll = NULL;
if (!hLocalDBDll)
{
LONG ec;
HKEY hkeyVersions = NULL;
HKEY hkeyVersion = NULL;
Version verHigh = {0};
Version verCurrent;
DWORD cchKeyName;
DWORD dwValueType;
WCHAR wszLocalDBDll[MAX_PATH+1];
DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk
HMODULE hLocalDBDllTemp = NULL;
if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions)))
{
goto Cleanup;
}
for (int i = 0; ; i++)
{
cchKeyName = 256;
if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL)))
{
if (ERROR_NO_MORE_ITEMS == ec)
{
break;
}
goto Cleanup;
}
if (!ParseVersion(&verCurrent))
{
continue; // invalid version syntax
}
if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] ||
(verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1]))
{
verHigh = verCurrent;
}
}
if (!verHigh.wszKeyName[0])
{
// ec must be ERROR_NO_MORE_ITEMS here
//
assert(ec == ERROR_NO_MORE_ITEMS);
// We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that
// LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists
// but it is empty.
//
ec = ERROR_FILE_NOT_FOUND;
goto Cleanup;
}
if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion)))
{
goto Cleanup;
}
if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll)))
{
goto Cleanup;
}
if (dwValueType != REG_SZ)
{
ec = ERROR_INVALID_DATA;
goto Cleanup;
}
// Ensure string value null-terminated
// Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose
//
wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0';
hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll);
if (NULL == hLocalDBDllTemp)
{
ec = GetLastError();
goto Cleanup;
}
if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL))
{
// We were the winner: we gave away our DLL handle
//
hLocalDBDllTemp = NULL;
}
ec = ERROR_SUCCESS;
Cleanup:
if (hLocalDBDllTemp)
FreeLibrary(hLocalDBDllTemp);
if (hkeyVersion)
RegCloseKey(hkeyVersion);
if (hkeyVersions)
RegCloseKey(hkeyVersions);
// Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing.
// In that case we should return the LocalDB specific error code
//
if (ec == ERROR_FILE_NOT_FOUND)
return LOCALDB_ERROR_NOT_INSTALLED;
if (ec != ERROR_SUCCESS)
return HRESULT_FROM_WIN32(ec);
}
FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn);
if (!pfn)
{
return HRESULT_FROM_WIN32(GetLastError());
}
*pfnLocalDBFn = pfn;
return S_OK;
}
// The following proxy functions forward calls to the latest LocalDB API DLL.
//
HRESULT __cdecl
LocalDBCreateInstance (
// I the LocalDB version (e.g. 11.0 or 11.0.1094.2)
__in_z PCWSTR wszVersion,
// I the instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
)
{
LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags);
}
HRESULT __cdecl
LocalDBStartInstance(
// I the instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags,
// O the buffer to store the connection string to the LocalDB instance
__out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection,
// I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null.
__inout_opt LPDWORD lpcchSqlConnection
)
{
LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection);
}
HRESULT __cdecl
LocalDBStopInstance (
// I the instance name
__in_z PCWSTR pInstanceName,
// I Available flags:
// LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately
// LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option
__in DWORD dwFlags,
// I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately
// without waiting for LocalDB instance to stop
__in ULONG ulTimeout
)
{
LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout);
}
HRESULT __cdecl
LocalDBDeleteInstance (
// I the instance name
__in_z PCWSTR pInstanceName,
// reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
)
{
LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags);
}
HRESULT __cdecl
LocalDBFormatMessage(
// I the LocalDB error code
__in HRESULT hrLocalDB,
// I Available flags:
// LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short,
// the error message will be truncated to fit into the buffer
__in DWORD dwFlags,
// I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used)
__in DWORD dwLanguageId,
// O the buffer to store the LocalDB error message
__out_ecount_z(*lpcchMessage) LPWSTR wszMessage,
// I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null. If the function succeeds
// contains the number of characters in the message, excluding the trailing null
__inout LPDWORD lpcchMessage
)
{
LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage);
}
HRESULT __cdecl
LocalDBGetInstances(
// O buffer with instance names
__out PTLocalDBInstanceName pInstanceNames,
// I/O on input has the number slots for instance names in the pInstanceNames buffer. On output,
// has the number of existing LocalDB instances
__inout LPDWORD lpdwNumberOfInstances
)
{
LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances);
}
HRESULT __cdecl
LocalDBGetInstanceInfo(
// I the instance name
__in_z PCWSTR wszInstanceName,
// O instance information
__out PLocalDBInstanceInfo pInfo,
// I Size of LocalDBInstanceInfo structure in bytes
__in DWORD cbInfo
)
{
LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo);
}
HRESULT __cdecl
LocalDBStartTracing()
{
LOCALDB_PROXY(LocalDBStartTracing)();
}
HRESULT __cdecl
LocalDBStopTracing()
{
LOCALDB_PROXY(LocalDBStopTracing)();
}
HRESULT __cdecl
LocalDBShareInstance(
// I the SID of the LocalDB instance owner
__in_opt PSID pOwnerSID,
// I the private name of LocalDB instance which should be shared
__in_z PCWSTR wszLocalDBInstancePrivateName,
// I the public shared name
__in_z PCWSTR wszSharedName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags)
{
LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags);
}
HRESULT __cdecl
LocalDBGetVersions(
// O buffer for installed LocalDB versions
__out PTLocalDBVersion pVersions,
// I/O on input has the number slots for versions in the pVersions buffer. On output,
// has the number of existing LocalDB versions
__inout LPDWORD lpdwNumberOfVersions
)
{
LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions);
}
HRESULT __cdecl
LocalDBUnshareInstance(
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags)
{
LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags);
}
HRESULT __cdecl
LocalDBGetVersionInfo(
// I LocalDB version string
__in_z PCWSTR wszVersion,
// O version information
__out PLocalDBVersionInfo pVersionInfo,
// I Size of LocalDBVersionInfo structure in bytes
__in DWORD cbVersionInfo)
{
LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo);
}
#endif
#endif // _SQLUSERINSTANCE_H_
//-----------------------------------------------------------------------------
// File: sqluserinstancemsgs.mc
//
// Copyright: Copyright (c) Microsoft Corporation
//-----------------------------------------------------------------------------
#ifndef _LOCALDB_MESSAGES_H_
#define _LOCALDB_MESSAGES_H_
// Header section
//
// Section with the LocalDB messages
//
//
// Values are 32 bit values laid out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +-+-+-+-+-+---------------------+-------------------------------+
// |S|R|C|N|r| Facility | Code |
// +-+-+-+-+-+---------------------+-------------------------------+
//
// where
//
// S - Severity - indicates success/fail
//
// 0 - Success
// 1 - Fail (COERROR)
//
// R - reserved portion of the facility code, corresponds to NT's
// second severity bit.
//
// C - reserved portion of the facility code, corresponds to NT's
// C field.
//
// N - reserved portion of the facility code. Used to indicate a
// mapped NT status value.
//
// r - reserved portion of the facility code. Reserved for internal
// use. Used to indicate HRESULT values that are not status
// values, but are instead message ids for display strings.
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
//
// Define the facility codes
//
#define FACILITY_LOCALDB 0x9C5
//
// Define the severity codes
//
#define LOCALDB_SEVERITY_SUCCESS 0x0
#define LOCALDB_SEVERITY_ERROR 0x2
//
// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER
//
// MessageText:
//
// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>.
//
#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L)
//
// MessageId: LOCALDB_ERROR_INVALID_PARAMETER
//
// MessageText:
//
// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation.
//
#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L)
//
// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION
//
// MessageText:
//
// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version.
//
#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L)
//
// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER
//
// MessageText:
//
// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%).
//
#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L)
//
// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG
//
// MessageText:
//
// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>.
//
#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L)
//
// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER
//
// MessageText:
//
// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>.
//
#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L)
//
// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY
//
// MessageText:
//
// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE
//
// MessageText:
//
// The specified LocalDB instance does not exist.
//
#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L)
//
// MessageId: LOCALDB_ERROR_INTERNAL_ERROR
//
// MessageText:
//
// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L)
//
// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY
//
// MessageText:
//
// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L)
//
// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED
//
// MessageText:
//
// Error occurred during LocalDB instance startup: SQL Server process failed to start.
//
#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT
//
// MessageText:
//
// LocalDB instance is corrupted. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL)
//
// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS
//
// MessageText:
//
// Error occurred during LocalDB instance startup: unable to create the SQL Server process.
//
#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION
//
// MessageText:
//
// The specified LocalDB version is not available on this computer.
//
#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID
//
// MessageText:
//
// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown.
//
#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED
//
// MessageText:
//
// Stop operation for LocalDB instance failed to complete within the specified time.
//
#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE
//
// MessageText:
//
// Error getting the localized error message. The specified error code is unknown.
//
#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L)
//
// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED
//
// MessageText:
//
// The LocalDB version available on this workstation is lower than the requested LocalDB version.
//
#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L)
//
// MessageId: LOCALDB_ERROR_INSTANCE_BUSY
//
// MessageText:
//
// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again.
//
#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L)
//
// MessageId: LOCALDB_ERROR_INVALID_OPERATION
//
// MessageText:
//
// Default LocalDB instances cannot be created, stopped or deleted manually.
//
#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L)
//
// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER
//
// MessageText:
//
// The buffer passed to the LocalDB instance API method has insufficient size.
//
#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L)
//
// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT
//
// MessageText:
//
// Timeout occurred inside the LocalDB instance API method.
//
#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L)
// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED.
// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API.
//
//
// MessageId: LOCALDB_ERROR_XEVENT_FAILED
//
// MessageText:
//
// Failed to start XEvent engine within the LocalDB Instance API.
//
#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L)
//
// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED
//
// MessageText:
//
// Cannot create an automatic instance. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L)
//
// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN
//
// MessageText:
//
// Cannot create a shared instance. The specified shared instance name is already in use.
//
#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L)
//
// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER
//
// MessageText:
//
// API caller is not LocalDB instance owner.
//
#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL)
//
// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME
//
// MessageText:
//
// Specified LocalDB instance name is invalid.
//
#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED
//
// MessageText:
//
// The specified LocalDB instance is already shared with different shared name.
//
#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED
//
// MessageText:
//
// The specified LocalDB instance is not shared.
//
#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL)
//
// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED
//
// MessageText:
//
// Administrator privileges are required in order to execute this operation.
//
#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL)
//
// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES
//
// MessageText:
//
// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances.
//
#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL)
//
// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH
//
// MessageText:
//
// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user.
//
#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L)
//
// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES
//
// MessageText:
//
// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL".
//
#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L)
// Detailed error descriptions
//
// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING
//
// MessageText:
//
// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L)
//
// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER
//
// MessageText:
//
// Cannot access LocalDB instance folder: %1
//
#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L)
//
// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG
//
// MessageText:
//
// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L)
//
// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING
//
// MessageText:
//
// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L)
//
// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG
//
// MessageText:
//
// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L)
//
// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID
//
// MessageText:
//
// Data directory for LocalDB instance is invalid: %1
//
#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT
//
// MessageText:
//
// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4)
//
#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR
//
// MessageText:
//
// LocalDB instance API: XEvent error: %1
//
#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L)
//
// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED
//
// MessageText:
//
// LocalDB installation is corrupted. Reinstall the LocalDB.
//
#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L)
//
// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION
//
// MessageText:
//
// LocalDB XEvent error: cannot determine %ProgramFiles% folder location.
//
#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE
//
// MessageText:
//
// LocalDB XEvent error: Cannot initialize XEvent engine.
//
#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE
//
// MessageText:
//
// LocalDB XEvent error: Cannot find XEvents configuration file: %1
//
#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE
//
// MessageText:
//
// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1
// HRESULT returned: %2
//
#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG
//
// MessageText:
//
// LocalDB XEvent error: XEvents engine configuration file too long
//
#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL)
//
// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED
//
// MessageText:
//
// CoInitializeEx API failed. HRESULT returned: %1
//
#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL)
//
// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID
//
// MessageText:
//
// LocalDB parent instance version is invalid: %1
//
#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL)
//
// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR
//
// MessageText:
//
// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5
//
#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L)
//
// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT
//
// MessageText:
//
// Function %1 returned %2 at line %3.
//
#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L)
//
#endif // _LOCALDB_MESSAGES_H_
#endif //__msodbcsql_h__

View file

@ -1,1417 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// file: pdo_dbh.cpp
//
// Contents: Implements the PDO object for PDO_SQLSRV
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "pdo_sqlsrv.h"
#include <psapi.h>
#include <windows.h>
#include <winver.h>
#include <string>
#include <sstream>
typedef const zend_function_entry pdo_sqlsrv_function_entry;
// *** internal variables and constants ***
namespace {
const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;";
const char LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer
const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)";
const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes
// List of PDO supported connection options.
namespace PDOConnOptionNames {
const char Server[] = "Server";
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_Option[] = "MultipleActiveResultSets";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
const char QuotedId[] = "QuotedId";
const char TraceFile[] = "TraceFile";
const char TraceOn[] = "TraceOn";
const char TrustServerCertificate[] = "TrustServerCertificate";
const char TransactionIsolation[] = "TransactionIsolation";
const char WSID[] = "WSID";
}
enum PDO_CONN_OPTIONS {
PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
};
enum PDO_STMT_OPTIONS {
PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC,
PDO_STMT_OPTION_DIRECT_QUERY,
PDO_STMT_OPTION_CURSOR_SCROLL_TYPE,
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
PDO_STMT_OPTION_EMULATE_PREPARES,
};
// List of all the statement options supported by this driver.
const stmt_option PDO_STMT_OPTS[] = {
{ NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, new stmt_option_query_timeout },
{ NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, new stmt_option_scrollable },
{ NULL, 0, PDO_STMT_OPTION_ENCODING, new stmt_option_encoding },
{ NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, new stmt_option_direct_query },
{ NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, new stmt_option_cursor_scroll_type },
{ NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, new stmt_option_buffered_query_limit },
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, new stmt_option_emulate_prepares },
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, NULL},
};
// boolean connection string
struct pdo_bool_conn_str_func
{
static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC );
};
struct pdo_txn_isolation_conn_attr_func
{
static void func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC );
};
template <unsigned int Attr>
struct pdo_int_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." );
int val = atoi( Z_STRVAL_P( value ));
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( val ), SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
template <unsigned int Attr>
struct pdo_bool_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( core_str_zval_is_true( value )),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
// statement options related functions
void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* options_ht,
zval** data TSRMLS_DC );
void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* pdo_stmt_options_ht TSRMLS_DC );
} // namespace
// List of all connection options supported by this driver.
const connection_option PDO_CONN_OPTS[] = {
{
PDOConnOptionNames::Server,
sizeof( PDOConnOptionNames::Server ),
PDO_CONN_OPTION_SERVER,
NULL,
0,
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::APP,
sizeof( PDOConnOptionNames::APP ),
SQLSRV_CONN_OPTION_APP,
ODBCConnOptions::APP,
sizeof( ODBCConnOptions::APP ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::ApplicationIntent,
sizeof( PDOConnOptionNames::ApplicationIntent ),
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
ODBCConnOptions::ApplicationIntent,
sizeof( ODBCConnOptions::ApplicationIntent ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::AttachDBFileName,
sizeof( PDOConnOptionNames::AttachDBFileName ),
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
ODBCConnOptions::AttachDBFileName,
sizeof( ODBCConnOptions::AttachDBFileName ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::ConnectionPooling,
sizeof( PDOConnOptionNames::ConnectionPooling ),
SQLSRV_CONN_OPTION_CONN_POOLING,
ODBCConnOptions::ConnectionPooling,
sizeof( ODBCConnOptions::ConnectionPooling ),
CONN_ATTR_BOOL,
conn_null_func::func
},
{
PDOConnOptionNames::Database,
sizeof( PDOConnOptionNames::Database ),
SQLSRV_CONN_OPTION_DATABASE,
ODBCConnOptions::Database,
sizeof( ODBCConnOptions::Database ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::Encrypt,
sizeof( PDOConnOptionNames::Encrypt ),
SQLSRV_CONN_OPTION_ENCRYPT,
ODBCConnOptions::Encrypt,
sizeof( ODBCConnOptions::Encrypt ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::Failover_Partner,
sizeof( PDOConnOptionNames::Failover_Partner ),
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
ODBCConnOptions::Failover_Partner,
sizeof( ODBCConnOptions::Failover_Partner ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::LoginTimeout,
sizeof( PDOConnOptionNames::LoginTimeout ),
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
ODBCConnOptions::LoginTimeout,
sizeof( ODBCConnOptions::LoginTimeout ),
CONN_ATTR_INT,
pdo_int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
},
{
PDOConnOptionNames::MARS_Option,
sizeof( PDOConnOptionNames::MARS_Option ),
SQLSRV_CONN_OPTION_MARS,
ODBCConnOptions::MARS_ODBC,
sizeof( ODBCConnOptions::MARS_ODBC ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::MultiSubnetFailover,
sizeof( PDOConnOptionNames::MultiSubnetFailover ),
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
ODBCConnOptions::MultiSubnetFailover,
sizeof( ODBCConnOptions::MultiSubnetFailover ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::QuotedId,
sizeof( PDOConnOptionNames::QuotedId ),
SQLSRV_CONN_OPTION_QUOTED_ID,
ODBCConnOptions::QuotedId,
sizeof( ODBCConnOptions::QuotedId ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::TraceFile,
sizeof( PDOConnOptionNames::TraceFile ),
SQLSRV_CONN_OPTION_TRACE_FILE,
ODBCConnOptions::TraceFile,
sizeof( ODBCConnOptions::TraceFile ),
CONN_ATTR_STRING,
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
},
{
PDOConnOptionNames::TraceOn,
sizeof( PDOConnOptionNames::TraceOn ),
SQLSRV_CONN_OPTION_TRACE_ON,
ODBCConnOptions::TraceOn,
sizeof( ODBCConnOptions::TraceOn ),
CONN_ATTR_BOOL,
pdo_bool_conn_attr_func<SQL_ATTR_TRACE>::func
},
{
PDOConnOptionNames::TransactionIsolation,
sizeof( PDOConnOptionNames::TransactionIsolation ),
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
ODBCConnOptions::TransactionIsolation,
sizeof( ODBCConnOptions::TransactionIsolation ),
CONN_ATTR_INT,
pdo_txn_isolation_conn_attr_func::func
},
{
PDOConnOptionNames::TrustServerCertificate,
sizeof( PDOConnOptionNames::TrustServerCertificate ),
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
ODBCConnOptions::TrustServerCertificate,
sizeof( ODBCConnOptions::TrustServerCertificate ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::WSID,
sizeof( PDOConnOptionNames::WSID ),
SQLSRV_CONN_OPTION_WSID,
ODBCConnOptions::WSID,
sizeof( ODBCConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
};
// close the connection
int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC );
// execute queries
int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql,
long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC );
long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC );
// transaction support functions
int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC );
int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC );
int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC );
// attribute functions
int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC );
int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, long attr, zval *return_value TSRMLS_DC );
// return more information
int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt,
zval *info TSRMLS_DC);
// return the last id generated by an executed SQL statement
char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, unsigned int* len TSRMLS_DC );
// additional methods are supported in this function
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC );
// quote a string, meaning put quotes around it and escape any quotes within it
int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, int unquotedlen, char **quoted, int* quotedlen,
enum pdo_param_type paramtype TSRMLS_DC );
struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = {
pdo_sqlsrv_dbh_close,
pdo_sqlsrv_dbh_prepare,
pdo_sqlsrv_dbh_do,
pdo_sqlsrv_dbh_quote,
pdo_sqlsrv_dbh_begin,
pdo_sqlsrv_dbh_commit,
pdo_sqlsrv_dbh_rollback,
pdo_sqlsrv_dbh_set_attr,
pdo_sqlsrv_dbh_last_id,
pdo_sqlsrv_dbh_return_error,
pdo_sqlsrv_dbh_get_attr,
NULL, // check liveness not implemented
pdo_sqlsrv_get_driver_methods,
NULL // request shutdown not implemented
};
// log a function entry point
#define PDO_LOG_DBH_ENTRY \
{ \
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data ); \
driver_dbh->set_func( __FUNCTION__ ); \
LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \
}
// constructor for the internal object for connections
pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) :
sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ),
stmts( NULL ),
direct_query( false ),
query_timeout( QUERY_TIMEOUT_INVALID ),
client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size ) )
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." );
}
}
// pdo_sqlsrv_db_handle_factory
// Maps to PDO::__construct.
// Factory method called by the PDO driver manager to create a SQLSRV PDO connection.
// Does the following things:
// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION.
// (If an error occurs in this function, the PDO specification mandates that
// an exception be thrown, regardless of the error mode setting.)
// 2. Processes the driver options.
// 3. Creates a core_conn object by calling core_sqlsrv_connect.
// 4. Restores the previous error mode on success.
// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory
// Parameters:
// dbh - The PDO managed structure for the connection.
// driver_options - A HashTable (within the zval) of options to use when creating the connection.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC)
{
LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" );
hash_auto_ptr pdo_conn_options_ht;
pdo_error_mode prev_err_mode = dbh->error_mode;
// must be done in all cases so that even a failed connection can query the
// object for errors.
dbh->methods = &pdo_sqlsrv_dbh_methods;
dbh->driver_data = NULL;
zval** temp_server_z = NULL;
sqlsrv_malloc_auto_ptr<conn_string_parser> dsn_parser;
zval_auto_ptr server_z;
try {
// no matter what the error mode, we want exceptions thrown if the connection fails
// to happen (per the PDO spec)
dbh->error_mode = PDO_ERRMODE_EXCEPTION;
g_henv_cp->set_driver( dbh );
g_henv_ncp->set_driver( dbh );
CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) {
throw core::CoreException();
}
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_conn_options_ht );
core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, NULL /*hashfn*/,
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
// Either of g_henv_cp or g_henv_ncp can be used to propogate the error.
dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source,
dbh->data_source_len, pdo_conn_options_ht );
dsn_parser->parse_conn_string( TSRMLS_C );
// Extract the server name
zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER, (void**)&temp_server_z );
CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) {
throw pdo::PDOException();
}
server_z = *temp_server_z;
// Add a reference to the option value since we are deleting it from the hashtable
zval_add_ref( &server_z );
zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER );
sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn<pdo_sqlsrv_dbh>, Z_STRVAL_P( server_z ),
dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error,
PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC );
SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." );
// set the driver_data and methods to complete creation of the PDO object
dbh->driver_data = conn;
dbh->error_mode = prev_err_mode; // reset the error mode
dbh->alloc_own_columns = 1; // we do our own memory management for columns
dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types
}
catch( core::CoreException& ) {
dbh->error_mode = prev_err_mode; // reset the error mode
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" );
}
return 1;
}
// pdo_sqlsrv_dbh_close
// Maps to PDO::__destruct.
// Called when a PDO object is to be destroyed.
// By the time this function is called, PDO has already made sure that
// all statements are disposed and the PDO object is the last item destroyed.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// Always returns 1 for success.
int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC )
{
LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" );
// if the connection didn't complete properly, driver_data isn't initialized.
if( dbh->driver_data == NULL ) {
return 1;
}
PDO_RESET_DBH_ERROR;
// call the core layer close
core_sqlsrv_close( reinterpret_cast<sqlsrv_conn*>( dbh->driver_data ) TSRMLS_CC );
dbh->driver_data = NULL;
// always return success that the connection is closed
return 1;
}
// pdo_sqlsrv_dbh_prepare
// Called by PDO::prepare and PDOStatement::__construct.
// Creates a statement and prepares it for execution by PDO
// Paramters:
// dbh - The PDO managed connection object.
// sql - SQL query to be prepared.
// sql_len - Length of the sql query
// stmt - The PDO managed statement object.
// driver_options - User provided list of statement options.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql,
long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
hash_auto_ptr pdo_stmt_options_ht;
sqlsrv_malloc_auto_ptr<char> sql_rewrite;
int sql_rewrite_len = 0;
sqlsrv_malloc_auto_ptr<pdo_sqlsrv_stmt> driver_stmt;
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null");
try {
// assign the methods for the statement object. This is necessary even if the
// statement fails so the user can retrieve the error information.
stmt->methods = &pdo_sqlsrv_stmt_methods;
stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_stmt_options_ht );
core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, NULL /*hashfn*/,
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
// Either of g_henv_cp or g_henv_ncp can be used to propogate the error.
validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC );
driver_stmt = static_cast<pdo_sqlsrv_stmt*>( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>,
pdo_stmt_options_ht, PDO_STMT_OPTS,
pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC ));
// if the user didn't set anything in the prepare options, then set the buffer limit
// to the value set on the connection.
if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) {
driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size;
}
// if the user didn't set anything in the prepare options, then set the query timeout
// to the value set on the connection.
if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) {
core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC );
}
// rewrite named parameters in the query to positional parameters if we aren't letting PDO do the
// parameter substitution for us
if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
// rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named
// parameters for consistency with the PDO MySQL and PDO ODBC drivers.
int zr = pdo_subst_named_params( stmt, const_cast<char*>( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC );
CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE ) {
throw core::CoreException();
}
// if parameter substitution happened, use that query instead of the original
if( sql_rewrite != NULL ) {
sql = sql_rewrite;
sql_len = sql_rewrite_len;
}
}
if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC );
}
else if( driver_stmt->direct_query ) {
if( driver_stmt->direct_query_subst_string ) {
// we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme
// and we use estrdup below to allocate the new string, which uses emalloc
efree( reinterpret_cast<void*>( const_cast<char*>( driver_stmt->direct_query_subst_string )));
}
driver_stmt->direct_query_subst_string = estrdup( sql );
driver_stmt->direct_query_subst_string_len = sql_len;
}
// else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
// set to the substituted query
stmt->driver_data = driver_stmt;
driver_stmt.transferred();
}
// everything is cleaned up by this point
// catch everything so the exception doesn't spill into the calling PDO code
catch( core::CoreException& ) {
if( driver_stmt ) {
driver_stmt->~pdo_sqlsrv_stmt();
}
// in the event that the statement caused an error that was copied to the connection, update the
// connection with the error's SQLSTATE.
if( driver_dbh->last_error() ) {
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
reinterpret_cast<const char*>( driver_dbh->last_error()->sqlstate ));
}
return 0;
}
// catch any errant exception and die
catch(...) {
DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." );
}
return 1;
}
// pdo_sqlsrv_dbh_do
// Maps to PDO::exec.
// Execute a SQL statement, such as an insert, update or delete, and return
// the number of rows affected.
// Parameters:
// dbh - the PDO connection object, which contains the ODBC handle
// sql - the query to execute
// sql_len - length of sql query
// Return
// # of rows affected, -1 for an error.
long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> driver_stmt;
long rows = 0;
// verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong
// thing to happen here.
SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN ));
try {
SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." );
// temp PDO statement used for error handling if something happens
pdo_stmt_t temp_stmt;
temp_stmt.dbh = dbh;
// allocate a full driver statement to take advantage of the error handling
driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>, NULL /*options_ht*/,
NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC );
driver_stmt->set_func( __FUNCTION__ );
core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, sql_len );
// since the user can give us a compound statement, we return the row count for the last set, and since the row count
// isn't guaranteed to be valid until all the results have been fetched, we fetch them all first.
if( core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) {
SQLRETURN r = SQL_SUCCESS;
do {
rows = core::SQLRowCount( driver_stmt TSRMLS_CC );
r = core::SQLMoreResults( driver_stmt TSRMLS_CC );
} while( r != SQL_NO_DATA );
}
// returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases
// naturally, so we override that here with no rows returned.
if( rows == -1 ) {
rows = 0;
}
}
catch( core::CoreException& ) {
// copy any errors on the statement to the connection so that the user sees them, since the statement is released
// before this method returns
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
reinterpret_cast<const char*>( driver_stmt->last_error()->sqlstate ));
driver_dbh->set_last_error( driver_stmt->last_error() );
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
return -1;
}
catch( ... ) {
DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." );
}
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
return rows;
}
// transaction support functions
// pdo_sqlsrv_dbh_begin
// Maps to PDO::beginTransaction.
// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn
// flag is maintained by PDO so we dont have to worry about it.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success.
int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
try {
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" );
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" );
DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" );
core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC );
return 1;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred.");
}
}
// pdo_sqlsrv_dbh_commit
// Maps to PDO::commit.
// Commits a transaction. Returns the connection to auto-commit mode.
// PDO throws error if PDO::commit is called on a connection that is not in an active
// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have
// to worry about it here.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success.
int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
try {
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" );
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" );
DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" );
core_sqlsrv_commit( driver_conn TSRMLS_CC );
return 1;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred.");
}
}
// pdo_sqlsrv_dbh_rollback
// Maps to PDO::rollback.
// Rolls back a transaction. Returns the connection in auto-commit mode.
// PDO throws error if PDO::rollBack is called on a connection that is not in an active
// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have
// to worry about it here.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success.
int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
try {
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" );
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" );
DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" );
core_sqlsrv_rollback( driver_conn TSRMLS_CC );
return 1;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred.");
}
}
// pdo_sqlsrv_dbh_set_attr
// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object.
// PDO driver manager calls this function directly after calling the factory
// method for PDO, for any attribute which is specified in the PDO constructor.
// Parameters:
// dbh - The PDO connection object maintained by PDO.
// attr - The attribute to be set.
// val - The value of the attribute to be set.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
try {
switch( attr ) {
case SQLSRV_ATTR_ENCODING:
{
long attr_value;
if( Z_TYPE_P( val ) != IS_LONG ) {
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING );
}
attr_value = Z_LVAL_P( val );
switch( attr_value ) {
case SQLSRV_ENCODING_DEFAULT:
// when default is applied to a connection, that means use UTF-8 encoding
driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 );
break;
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_UTF8:
driver_dbh->set_encoding( static_cast<SQLSRV_ENCODING>( attr_value ));
break;
default:
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING );
break;
}
}
break;
case SQLSRV_ATTR_DIRECT_QUERY:
driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false;
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) {
convert_to_string( val );
THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val ));
}
driver_dbh->query_timeout = Z_LVAL_P( val );
break;
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) {
convert_to_string( val );
THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val ));
}
driver_dbh->client_buffer_max_size = Z_LVAL_P( val );
break;
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
case PDO_ATTR_PREFETCH:
case PDO_ATTR_MAX_COLUMN_LEN:
case PDO_ATTR_CURSOR_NAME:
case PDO_ATTR_AUTOCOMMIT:
case PDO_ATTR_PERSISTENT:
case PDO_ATTR_TIMEOUT:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR );
}
// Read-only
case PDO_ATTR_SERVER_VERSION:
case PDO_ATTR_SERVER_INFO:
case PDO_ATTR_CLIENT_VERSION:
case PDO_ATTR_DRIVER_NAME:
case PDO_ATTR_CONNECTION_STATUS:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR );
}
// Statement level only
case PDO_ATTR_EMULATE_PREPARES:
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
}
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
break;
}
}
}
catch( pdo::PDOException& ) {
return 0;
}
return 1;
}
// pdo_sqlsrv_dbh_get_attr
// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object.
// Parameters:
// dbh - The PDO connection object maintained by PDO.
// attr - The attribute to get.
// return_value - zval in which to return the attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, long attr, zval *return_value TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
try {
switch( attr ) {
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
case PDO_ATTR_PREFETCH:
case PDO_ATTR_MAX_COLUMN_LEN:
case PDO_ATTR_CURSOR_NAME:
case PDO_ATTR_AUTOCOMMIT:
case PDO_ATTR_TIMEOUT:
{
// PDO does not throw "not supported" error message for these attributes.
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR );
}
// Statement level only
case PDO_ATTR_EMULATE_PREPARES:
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
}
case PDO_ATTR_STRINGIFY_FETCHES:
{
// For this attribute, if we dont set the return_value than PDO returns NULL.
ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) );
break;
}
case PDO_ATTR_SERVER_INFO:
{
core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC );
break;
}
case PDO_ATTR_SERVER_VERSION:
{
core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC );
break;
}
case PDO_ATTR_CLIENT_VERSION:
{
core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC );
//Add the PDO SQLSRV driver's file version
core::sqlsrv_add_assoc_string( *driver_dbh, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/
TSRMLS_CC );
break;
}
case SQLSRV_ATTR_ENCODING:
{
ZVAL_LONG( return_value, driver_dbh->encoding() );
break;
}
case SQLSRV_ATTR_QUERY_TIMEOUT:
{
ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout ));
break;
}
case SQLSRV_ATTR_DIRECT_QUERY:
{
ZVAL_BOOL( return_value, driver_dbh->direct_query );
break;
}
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
{
ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size );
break;
}
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
break;
}
}
return 1;
}
catch( core::CoreException& ) {
return 0;
}
}
// Called by PDO::errorInfo and PDOStatement::errorInfo.
// Returns the error info.
// Parameters:
// dbh - The PDO managed connection object.
// stmt - The PDO managed statement object.
// info - zval in which to return the error info.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt,
zval *info TSRMLS_DC)
{
SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." );
sqlsrv_error* ctx_error = NULL;
if( stmt ) {
ctx_error = static_cast<sqlsrv_stmt*>( stmt->driver_data )->last_error();
}
else {
ctx_error = static_cast<sqlsrv_conn*>( dbh->driver_data )->last_error();
}
pdo_sqlsrv_retrieve_context_error( ctx_error, info );
return 1;
}
// pdo_sqlsrv_dbh_last_id
// Maps to PDO::lastInsertId.
// Returns the last id generated by an executed SQL statement
// Parameters:
// dbh - The PDO managed connection object.
// name - Table name.
// len - Length of the name.
// Return:
// Returns the last insert id as a string.
char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out unsigned int* len TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
// turn off any error handling for last_id
pdo_error_mode prev_err_mode = dbh->error_mode;
dbh->error_mode = PDO_ERRMODE_SILENT;
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> driver_stmt;
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
sqlsrv_malloc_auto_ptr<char> id_str;
id_str = reinterpret_cast<char*>( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN ));
try {
char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ];
if( name == NULL ) {
strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY );
}
else {
char* quoted_table = NULL;
int quoted_len = 0;
int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), &quoted_table, &quoted_len, PDO_PARAM_NULL TSRMLS_CC );
SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name." );
sprintf_s( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table );
sqlsrv_free( quoted_table );
}
// temp PDO statement used for error handling if something happens
pdo_stmt_t temp_stmt;
temp_stmt.dbh = dbh;
// allocate a full driver statement to take advantage of the error handling
driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>, NULL /*options_ht*/,
NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC );
driver_stmt->set_func( __FUNCTION__ );
// execute the last insert id query
core::SQLExecDirect( driver_stmt, last_insert_id_query TSRMLS_CC );
core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC );
SQLSRV_STATIC_ASSERT( sizeof( SQLLEN ) == sizeof( unsigned int ));
SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN,
reinterpret_cast<SQLLEN*>( len ), false TSRMLS_CC );
CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt,
PDO_SQLSRV_ERROR_LAST_INSERT_ID ) {
throw core::CoreException();
}
driver_stmt->~sqlsrv_stmt();
}
catch( core::CoreException& ) {
// copy any errors on the statement to the connection so that the user sees them, since the statement is released
// before this method returns
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
reinterpret_cast<const char*>( driver_stmt->last_error()->sqlstate ));
driver_dbh->set_last_error( driver_stmt->last_error() );
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
strcpy_s( id_str.get(), 1, "" );
*len = 0;
}
char* ret_id_str = id_str.get();
id_str.transferred();
// restore error handling to its previous mode
dbh->error_mode = prev_err_mode;
return ret_id_str;
}
// pdo_sqlsrv_dbh_quote
// Maps to PDO::quote. As the name says, this function quotes a string.
// Always returns a valid string unless memory allocation fails.
// Parameters:
// dbh - The PDO managed connection object.
// unquoted - The unquoted string to be quoted.
// unquoted_len - Length of the unquoted string.
// quoted - Buffer for output string.
// quoted_len - Length of the output string.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, int unquoted_len, char **quoted, int* quoted_len,
enum pdo_param_type /*paramtype*/ TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
// count the number of quotes needed
unsigned int quotes_needed = 2; // the initial start and end quotes of course
for( int index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) {
if( unquoted[ index ] == '\'' ) {
++quotes_needed;
}
}
*quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator.
*quoted = reinterpret_cast<char*>( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator.
unsigned int out_current = 0;
// insert initial quote
(*quoted)[ out_current++ ] ='\'';
for( int index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) {
if( unquoted[ index ] == '\'' ) {
(*quoted)[ out_current++ ] = '\'';
(*quoted)[ out_current++ ] = '\'';
}
else {
(*quoted)[ out_current++ ] = unquoted[ index ];
}
}
// trailing quote and null terminator
(*quoted)[ out_current++ ] ='\'';
(*quoted)[ out_current ] = '\0';
return 1;
}
// This method is not implemented by this driver.
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) {
return NULL;
}
return NULL; // to avoid a compiler warning
}
namespace {
// Maps the PDO driver specific statement option/attribute constants to the core layer
// statement option/attribute constants.
void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* options_ht,
zval** data TSRMLS_DC )
{
unsigned long option_key = -1;
switch( key ) {
case PDO_ATTR_CURSOR:
option_key = SQLSRV_STMT_OPTION_SCROLLABLE;
break;
case SQLSRV_ATTR_ENCODING:
option_key = PDO_STMT_OPTION_ENCODING;
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT;
break;
case PDO_ATTR_STATEMENT_CLASS:
break;
case SQLSRV_ATTR_DIRECT_QUERY:
option_key = PDO_STMT_OPTION_DIRECT_QUERY;
break;
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE;
break;
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE;
break;
case PDO_ATTR_EMULATE_PREPARES:
option_key = PDO_STMT_OPTION_EMULATE_PREPARES;
break;
default:
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
throw core::CoreException();
}
break;
}
// if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it
if( option_key != -1 ) {
zval_add_ref( data );
core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, (void**)data, sizeof(zval*) TSRMLS_CC );
}
}
// validate_stmt_options
// Iterates through the list of statement options provided by the user and validates them
// against the list of statement options provided by this driver. After validation
// creates a Hashtable of statement options to be sent to the core layer for processing.
// Parameters:
// ctx - The current context.
// stmt_options - The user provided list of statement options.
// pdo_stmt_options_ht - Output hashtable of statement options.
void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* pdo_stmt_options_ht TSRMLS_DC )
{
try {
if( stmt_options ) {
HashTable* options_ht = Z_ARRVAL_P( stmt_options );
for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS;
zend_hash_move_forward( options_ht )) {
int type = HASH_KEY_NON_EXISTANT;
char *key = NULL;
unsigned int key_len = 0;
unsigned long int_key = -1;
zval** data;
int result = 0;
type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &int_key, 0, NULL );
CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
throw core::CoreException();
}
core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, (void**) &data TSRMLS_CC );
add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC );
}
}
}
catch( core::CoreException& ) {
throw;
}
}
void pdo_bool_conn_str_func::func(connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC )
{
TSRMLS_C;
char const* val_str = "no";
if( core_str_zval_is_true( value ) ) {
val_str = "yes";
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
}
void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn,
std::string& /*conn_str*/ TSRMLS_DC )
{
try {
SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." );
const char* val = Z_STRVAL_P( value_z );
int val_len = Z_STRLEN_P( value_z );
long out_val = SQL_TXN_READ_COMMITTED;
// READ_COMMITTED
if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 )
&& !_stricmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) {
out_val = SQL_TXN_READ_COMMITTED;
}
// READ_UNCOMMITTED
else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 )
&& !_stricmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) {
out_val = SQL_TXN_READ_UNCOMMITTED;
}
// REPEATABLE_READ
else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 )
&& !_stricmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) {
out_val = SQL_TXN_REPEATABLE_READ;
}
// SERIALIZABLE
else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 )
&& !_stricmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) {
out_val = SQL_TXN_SERIALIZABLE;
}
// SNAPSHOT
else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 )
&& !_stricmp( val, PDOTxnIsolationValues::SNAPSHOT ))) {
out_val = SQL_TXN_SS_SNAPSHOT;
}
else {
CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) {
throw core::CoreException();
}
}
core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast<SQLPOINTER>( out_val ), SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
} // namespace

View file

@ -1,401 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_init.cpp
//
// Contents: initialization routines for PDO_SQLSRV
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "pdo_sqlsrv.h"
#include <psapi.h>
ZEND_GET_MODULE(g_pdo_sqlsrv)
extern "C" {
ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv);
}
// module global variables (initialized in minit and freed in mshutdown)
HashTable* g_pdo_errors_ht = NULL;
// henv context for creating connections
sqlsrv_context* g_henv_cp;
sqlsrv_context* g_henv_ncp;
namespace {
pdo_driver_t pdo_sqlsrv_driver = {
PDO_DRIVER_HEADER(sqlsrv),
pdo_sqlsrv_db_handle_factory
};
const char PDO_DLL_NAME[] = "php_pdo.dll";
// PHP_DEBUG is always defined as either 0 or 1
#if PHP_DEBUG == 1 && !defined(ZTS)
const char PHP_DLL_NAME[] = "php5_debug.dll";
#elif PHP_DEBUG == 1 && defined(ZTS)
const char PHP_DLL_NAME[] = "php5ts_debug.dll";
#elif PHP_DEBUG == 0 && !defined(ZTS)
const char PHP_DLL_NAME[] = "php5.dll";
#elif PHP_DEBUG == 0 && defined(ZTS)
const char PHP_DLL_NAME[] = "php5ts.dll";
#else
#error Invalid combination of PHP_DEBUG and ZTS macros
#endif
typedef PDO_API int (*pdo_register_func)(pdo_driver_t *);
// function pointers to register and unregister this driver
pdo_register_func pdo_register_driver;
pdo_register_func pdo_unregister_driver;
// functions to register SQLSRV constants with the PDO class
// (It's in all CAPS so it looks like the Zend macros that do similar work)
void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC );
void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC );
// return the Zend class entry for the PDO dbh (connection) class
zend_class_entry* (*pdo_get_dbh_class)( void );
struct sqlsrv_attr_pdo_constant {
const char *name;
int value;
};
// forward decl for table
extern sqlsrv_attr_pdo_constant pdo_attr_constants[];
}
static zend_module_dep pdo_sqlsrv_depends[] = {
ZEND_MOD_REQUIRED("pdo")
{NULL, NULL, NULL}
};
// argument info structures for functions, arranged alphabetically.
// see zend_API.h in the PHP sources for more information about these macros
// function table with associated arginfo structures
zend_function_entry pdo_sqlsrv_functions[] = {
{NULL, NULL, NULL} // no functions directly defined by this driver
};
// the structure returned to Zend that exposes the extension to the Zend engine.
// this structure is defined in zend_modules.h in the PHP sources
zend_module_entry g_pdo_sqlsrv_module_entry =
{
STANDARD_MODULE_HEADER_EX,
NULL,
pdo_sqlsrv_depends,
"pdo_sqlsrv",
pdo_sqlsrv_functions, // exported function table
// initialization and shutdown functions
PHP_MINIT(pdo_sqlsrv),
PHP_MSHUTDOWN(pdo_sqlsrv),
PHP_RINIT(pdo_sqlsrv),
PHP_RSHUTDOWN(pdo_sqlsrv),
PHP_MINFO(pdo_sqlsrv),
// version of the extension. Matches the version resource of the extension dll
VER_FILEVERSION_STR,
PHP_MODULE_GLOBALS(pdo_sqlsrv),
NULL,
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
// functions dynamically linked from the PDO (or PHP) dll and called by other parts of the driver
zend_class_entry* (*pdo_get_exception_class)( void );
int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, int inquery_len,
char **outquery, int *outquery_len TSRMLS_DC);
// Module initialization
// This function is called once per execution of the Zend engine
PHP_MINIT_FUNCTION(pdo_sqlsrv)
{
SQLSRV_UNUSED( type );
// our global variables are initialized in the RINIT function
#if defined(ZTS)
if( ts_allocate_id( &pdo_sqlsrv_globals_id,
sizeof( zend_pdo_sqlsrv_globals ),
(ts_allocate_ctor) NULL,
(ts_allocate_dtor) NULL ) == 0 )
return FAILURE;
#endif
core_sqlsrv_register_logger( pdo_sqlsrv_log );
REGISTER_INI_ENTRIES();
LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" );
// PHP extensions may be either external DLLs loaded by PHP or statically compiled into the PHP dll
// This becomes an issue when we are dependent on other extensions, e.g. PDO. Normally this is solved
// by the build process by linking our extension to the appropriate import library (either php*.dll or php_pdo.dll)
// However, this leaves us with a problem that the extension has a dependency on that build type.
// Since we don't distribute our extension with PHP directly (yet), it would mean that we would have to have SKUs
// for both types of PDO builds, internal and external. Rather than this, we just dynamically link the PDO routines we call
// against either the PDO dll if it exists and is loaded, otherwise against the PHP dll directly.
DWORD needed = 0;
HANDLE hprocess = GetCurrentProcess();
HMODULE pdo_hmodule;
pdo_hmodule = GetModuleHandle( PDO_DLL_NAME );
if( pdo_hmodule == 0 ) {
pdo_hmodule = GetModuleHandle( PHP_DLL_NAME );
if( pdo_hmodule == NULL ) {
LOG( SEV_ERROR, "Failed to get PHP module handle." );
return FAILURE;
}
}
pdo_register_driver = reinterpret_cast<pdo_register_func>( GetProcAddress( pdo_hmodule, "php_pdo_register_driver" ));
if( pdo_register_driver == NULL ) {
LOG( SEV_ERROR, "Failed to register driver." );
return FAILURE;
}
pdo_unregister_driver = reinterpret_cast<pdo_register_func>( GetProcAddress( pdo_hmodule, "php_pdo_unregister_driver" ));
if( pdo_unregister_driver == NULL ) {
LOG( SEV_ERROR, "Failed to register driver." );
return FAILURE;
}
pdo_get_exception_class = reinterpret_cast<zend_class_entry* (*)(void)>( GetProcAddress( pdo_hmodule,
"php_pdo_get_exception" ));
if( pdo_get_exception_class == NULL ) {
LOG( SEV_ERROR, "Failed to register driver." );
return FAILURE;
}
pdo_get_dbh_class = reinterpret_cast<zend_class_entry* (*)(void)>( GetProcAddress( pdo_hmodule, "php_pdo_get_dbh_ce" ));
if( pdo_get_dbh_class == NULL ) {
LOG( SEV_ERROR, "Failed to register driver." );
return FAILURE;
}
pdo_subst_named_params =
reinterpret_cast<int (*)(pdo_stmt_t *stmt, char *inquery, int inquery_len,
char **outquery, int *outquery_len TSRMLS_DC)>(
GetProcAddress( pdo_hmodule, "pdo_parse_params" ));
if( pdo_subst_named_params == NULL ) {
LOG( SEV_ERROR, "Failed to register driver." );
return FAILURE;
}
// initialize list of pdo errors
g_pdo_errors_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
int zr = ::zend_hash_init( g_pdo_errors_ht, 50, NULL, NULL, 1 );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to initialize the PDO errors hashtable." );
return FAILURE;
}
for( int i = 0; PDO_ERRORS[ i ].error_code != -1; ++i ) {
zr = ::zend_hash_index_update( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code,
&( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ), NULL );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." );
return FAILURE;
}
}
try {
// register all attributes supported by this driver.
for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) {
REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC );
}
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC );
// retrieve the handles for the environments
core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC );
}
catch( ... ) {
return FAILURE;
}
pdo_register_driver( &pdo_sqlsrv_driver );
return SUCCESS;
}
// Module shutdown function
// Module shutdown function
// This function is called once per execution of the Zend engine
PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)
{
try {
SQLSRV_UNUSED( type );
UNREGISTER_INI_ENTRIES();
pdo_unregister_driver( &pdo_sqlsrv_driver );
// clean up the list of pdo errors
zend_hash_destroy( g_pdo_errors_ht );
pefree( g_pdo_errors_ht, 1 /*persistent*/ );
core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp );
}
catch( ... ) {
LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" );
return FAILURE;
}
return SUCCESS;
}
// Request initialization function
// This function is called once per PHP script execution
PHP_RINIT_FUNCTION(pdo_sqlsrv)
{
SQLSRV_UNUSED( module_number );
SQLSRV_UNUSED( type );
LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" );
// verify memory at the end of the request (in debug mode only)
full_mem_check(MEMCHECK_SILENT);
return SUCCESS;
}
// Request shutdown
// Called at the end of a script's execution
PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv)
{
SQLSRV_UNUSED( module_number );
SQLSRV_UNUSED( type );
LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" );
// verify memory at the end of the request (in debug mode only)
full_mem_check(MEMCHECK_SILENT);
return SUCCESS;
}
// Called for php_info();
// Displays the INI settings registered and their current values
PHP_MINFO_FUNCTION(pdo_sqlsrv)
{
#if defined(ZTS)
SQLSRV_UNUSED( tsrm_ls );
#endif
php_info_print_table_start();
php_info_print_table_header(2, "pdo_sqlsrv support", "enabled");
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
// *** internal init functions ***
namespace {
// mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because
// we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT)
void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC )
{
zend_class_entry* zend_class = pdo_get_dbh_class();
SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: pdo_get_dbh_class failed" );
int zr = zend_declare_class_constant_long( zend_class, const_cast<char*>( name ), strlen( name ), value TSRMLS_CC );
if( zr == FAILURE ) {
throw core::CoreException();
}
}
void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC )
{
zend_class_entry* zend_class = pdo_get_dbh_class();
SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: pdo_get_dbh_class failed" );
int zr = zend_declare_class_constant_string( zend_class, const_cast<char*>( name ), strlen( name ), const_cast<char*>( value ) TSRMLS_CC );
if( zr == FAILURE ) {
throw core::CoreException();
}
}
// array of pdo constants.
sqlsrv_attr_pdo_constant pdo_attr_constants[] = {
// driver specific attributes
{ "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING },
{ "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT },
{ "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY },
{ "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE },
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
// PDO::PARAM_STR uses the size of the string in the variable
{ "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 },
// encoding attributes
{ "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT },
{ "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM },
{ "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY },
{ "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 },
// cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE
{ "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC },
{ "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC },
{ "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN },
{ "SQLSRV_CURSOR_BUFFERED" , SQLSRV_CURSOR_BUFFERED },
{ NULL , 0 } // terminate the table
};
}

View file

@ -1,362 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_parser.cpp
//
// Contents: Implements a parser to parse the PDO DSN.
//
// Copyright Microsoft Corporation
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "pdo_sqlsrv.h"
// Constructor
conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, __inout HashTable* conn_options_ht )
{
this->conn_str = dsn;
this->len = len;
this->conn_options_ht = conn_options_ht;
this->pos = -1;
this->ctx = &ctx;
}
// Move to the next character
inline bool conn_string_parser::next( void )
{
// if already at the end than return false
if( this->is_eos() ) {
return false;
}
SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" );
this->pos++;
if ( this->is_eos() ) {
return false;
}
return true;
}
// Check for end of string.
inline bool conn_string_parser::is_eos( void )
{
if( this->pos == len )
{
return true; // EOS
}
SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" );
return false;
}
// Check for white space.
inline bool conn_string_parser::is_white_space( char c )
{
if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) {
return true;
}
return false;
}
// Discard any trailing white spaces.
int conn_string_parser::discard_trailing_white_spaces( const char* str, int len )
{
const char* end = str + ( len - 1 );
while(( this->is_white_space( *end ) ) && (len > 0) ) {
len--;
end--;
}
return len;
}
// Discard white spaces.
bool conn_string_parser::discard_white_spaces()
{
if( this->is_eos() ) {
return false;
}
while( this->is_white_space( this->conn_str[ pos ] )) {
if( !next() )
return false;
}
return true;
}
// Add a key-value pair to the hashtable of connection options.
void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC )
{
zval_auto_ptr value_z;
ALLOC_INIT_ZVAL( value_z );
if( len == 0 ) {
ZVAL_STRINGL( value_z, "", 0, 1 /*dup*/ );
}
else {
ZVAL_STRINGL( value_z, const_cast<char*>( value ), len, 1 /*dup*/ );
}
core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, (void**)&value_z,
sizeof(zval*) TSRMLS_CC );
zval_add_ref( &value_z );
}
// Validate a given DSN keyword.
void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC )
{
int new_len = discard_trailing_white_spaces( key, key_len );
for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i )
{
// discard the null terminator.
if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !_strnicmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) {
this->current_key = PDO_CONN_OPTS[ i ].conn_option_key;
this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name;
return;
}
}
// encountered an invalid key, throw error.
sqlsrv_malloc_auto_ptr<char> key_name;
key_name = static_cast<char*>( sqlsrv_malloc( new_len + 1 ));
memcpy( key_name, key, new_len );
key_name[ new_len ] = '\0';
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, key_name );
}
// Primary function which parses the connection string/DSN.
void conn_string_parser:: parse_conn_string( TSRMLS_D )
{
States state = FirstKeyValuePair; // starting state
int start_pos = -1;
try {
while( !this->is_eos() ) {
switch( state ) {
case FirstKeyValuePair:
{
// discard leading spaces
if( !next() || !discard_white_spaces() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS
}
state = Key;
break;
}
case Key:
{
start_pos = this->pos;
// read the key name
while( this->conn_str[ pos ] != '=' ) {
if( !next() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS
}
}
this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC );
state = Value;
break;
}
case Value:
{
SQLSRV_ASSERT(( this->conn_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: "
"Equal was expected" );
next(); // skip "="
// if EOS encountered after 0 or more spaces OR semi-colon encountered.
if( !discard_white_spaces() || this->conn_str[ pos ] == ';' ) {
add_key_value_pair( NULL, 0 TSRMLS_CC );
if( this->is_eos() ) {
break; // EOS
}
else {
// this->conn_str[ pos ] == ';'
state = NextKeyValuePair;
}
}
// if LCB
else if( this->conn_str[ pos ] == '{' ) {
start_pos = this->pos; // starting character is LCB
state = ValueContent1;
}
// If NonSP-LCB-SC
else {
start_pos = this->pos;
state = ValueContent2;
}
break;
}
case ValueContent1:
{
while ( this->conn_str[ pos ] != '}' ) {
if ( ! next() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name );
}
}
// If we reached here than RCB encountered
state = RCBEncountered;
break;
}
case ValueContent2:
{
while( this->conn_str[ pos ] != ';' ) {
if( ! next() ) {
break; //EOS
}
}
if( !this->is_eos() && this->conn_str[ pos ] == ';' ) {
// semi-colon encountered, so go to next key-value pair
state = NextKeyValuePair;
}
add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC );
SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )),
"conn_string_parser::parse_conn_string: Invalid state encountered " );
break;
}
case RCBEncountered:
{
// Read the next character after RCB.
if( !next() ) {
// EOS
add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC );
break;
}
SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" );
// if second RCB encountered than go back to ValueContent1
if( this->conn_str[ pos ] == '}' ) {
if( !next() ) {
// EOS after a second RCB is error
THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name );
}
state = ValueContent1;
break;
}
int end_pos = this->pos;
// discard any trailing white-spaces.
if( this->is_white_space( this->conn_str[ pos ] )) {
if( ! this->discard_white_spaces() ) {
//EOS
add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC );
break;
}
}
// if semi-colon than go to next key-value pair
if ( this->conn_str[ pos ] == ';' ) {
add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC );
state = NextKeyValuePair;
break;
}
// Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error.
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name );
break;
}
case NextKeyValuePair:
{
SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ),
"conn_string_parser::parse_conn_string: semi-colon was expected." );
// Call next() to skip the semi-colon.
if( !next() || !this->discard_white_spaces() ) {
// EOS
break;
}
if( this->conn_str[ pos ] == ';' ) {
// a second semi-colon is error case.
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos );
}
else {
// any other character leads to the next key
state = Key;
break;
}
} //case NextKeyValuePair
} // switch
} //while
}
catch( pdo::PDOException& ) {
throw;
}
}

View file

@ -1,393 +0,0 @@
#ifndef PDO_SQLSRV_H
#define PDO_SQLSRV_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_sqlsrv.h
//
// Contents: Declarations for the extension
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include "version.h"
extern "C" {
#include "pdo/php_pdo.h"
#include "pdo/php_pdo_driver.h"
#include "pdo/php_pdo_int.h"
}
#include <vector>
#include <map>
//*********************************************************************************************************************************
// Constants and Types
//*********************************************************************************************************************************
// sqlsrv driver specific PDO attributes
enum PDO_SQLSRV_ATTR {
// Currently there are only three custom attributes for this driver.
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
SQLSRV_ATTR_QUERY_TIMEOUT,
SQLSRV_ATTR_DIRECT_QUERY,
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
};
// valid set of values for TransactionIsolation connection option
namespace PDOTxnIsolationValues {
const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED";
const char READ_COMMITTED[] = "READ_COMMITTED";
const char REPEATABLE_READ[] = "REPEATABLE_READ";
const char SERIALIZABLE[] = "SERIALIZABLE";
const char SNAPSHOT[] = "SNAPSHOT";
}
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
extern "C" {
// request level variables
ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv)
unsigned int log_severity;
long client_buffer_max_size;
ZEND_END_MODULE_GLOBALS(pdo_sqlsrv)
ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv);
}
// macros used to access the global variables. Use these to make global variable access agnostic to threads
#ifdef ZTS
#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v)
#else
#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v
#endif
// INI settings and constants
// (these are defined as macros to allow concatenation as we do below)
#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size"
#define INI_PDO_SQLSRV_LOG "log_severity"
#define INI_PREFIX "pdo_sqlsrv."
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity,
zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong,
client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals )
PHP_INI_END()
// henv context for creating connections
extern sqlsrv_context* g_henv_cp;
extern sqlsrv_context* g_henv_ncp;
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// module global variables (initialized in minit and freed in mshutdown)
extern HashTable* g_pdo_errors_ht;
// module initialization
PHP_MINIT_FUNCTION(pdo_sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(pdo_sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(pdo_sqlsrv);
extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP
//*********************************************************************************************************************************
// PDO DSN Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN connection string.
class conn_string_parser
{
enum States
{
FirstKeyValuePair,
Key,
Value,
ValueContent1,
ValueContent2,
RCBEncountered,
NextKeyValuePair,
};
private:
const char* conn_str;
sqlsrv_context* ctx;
int len;
int pos;
unsigned int current_key;
const char* current_key_name;
HashTable* conn_options_ht;
inline bool next( void );
inline bool is_eos( void );
inline bool is_white_space( char c );
bool discard_white_spaces( void );
int discard_trailing_white_spaces( const char* str, int len );
void conn_string_parser::validate_key( const char *key, int key_len TSRMLS_DC );
void add_key_value_pair( const char* value, int len TSRMLS_DC );
public:
conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, __inout HashTable* conn_options_ht );
void parse_conn_string( TSRMLS_D );
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
extern const connection_option PDO_CONN_OPTS[];
int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC);
// a core layer pdo dbh object. This object inherits and overrides the statement factory
struct pdo_sqlsrv_dbh : public sqlsrv_conn {
zval* stmts;
bool direct_query;
long query_timeout;
long client_buffer_max_size;
pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC );
};
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_encoding : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC );
};
struct stmt_option_scrollable : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC );
};
struct stmt_option_direct_query : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC );
};
struct stmt_option_cursor_scroll_type : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC );
};
struct stmt_option_emulate_prepares : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC );
};
extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods;
// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary
struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
pdo_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) :
sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ),
direct_query( false ),
direct_query_subst_string( NULL ),
direct_query_subst_string_len( 0 ),
bound_column_param_types( NULL )
{
pdo_sqlsrv_dbh* db = static_cast<pdo_sqlsrv_dbh*>( c );
direct_query = db->direct_query;
}
virtual ~pdo_sqlsrv_stmt( void );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
// for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types
virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream );
bool direct_query; // flag set if the query should be executed directly or prepared
const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters
int direct_query_subst_string_len; // length of query string used for direct queries
// meta data for current result set
std::vector<field_meta_data*, sqlsrv_allocator< field_meta_data* > > current_meta_data;
pdo_param_type* bound_column_param_types;
};
//*********************************************************************************************************************************
// Error Handling Functions
//*********************************************************************************************************************************
// represents the mapping between an error_code and the corresponding error message.
struct pdo_error {
unsigned int error_code;
sqlsrv_error_const sqlsrv_error;
};
// called when an error occurs in the core layer. These routines are set as the error_callback in a
// context. The context is passed to this function since it contains the function
bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
va_list* print_args );
bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
va_list* print_args );
bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
va_list* print_args );
// pointer to the function to return the class entry for the PDO exception Set in MINIT
extern zend_class_entry* (*pdo_get_exception_class)( void );
// common routine to transfer a sqlsrv_context's error to a PDO zval
void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval );
// reset the errors from the last operation
inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC )
{
strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error
// release the last statement from the dbh so that error handling won't have a statement passed to it
if( dbh->query_stmt ) {
dbh->query_stmt = NULL;
zend_objects_store_del_ref( &dbh->query_stmt_zval TSRMLS_CC );
}
// if the driver isn't valid, just return (PDO calls close sometimes more than once?)
if( dbh->driver_data == NULL ) {
return;
}
// reset the last error on the sqlsrv_context
sqlsrv_context* ctx = static_cast<sqlsrv_conn*>( dbh->driver_data );
if( ctx->last_error() ) {
ctx->last_error().reset();
}
}
#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC );
inline void pdo_reset_stmt_error( pdo_stmt_t* stmt )
{
strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error
// if the driver isn't valid, just return (PDO calls close sometimes more than once?)
if( stmt->driver_data == NULL ) {
return;
}
// reset the last error on the sqlsrv_context
sqlsrv_context* ctx = static_cast<sqlsrv_stmt*>( stmt->driver_data );
if( ctx->last_error() ) {
ctx->last_error().reset();
}
}
#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt );
// validate the driver objects
#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); }
#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); }
//*********************************************************************************************************************************
// Utility Functions
//*********************************************************************************************************************************
// List of PDO specific error messages.
enum PDO_ERROR_CODES {
PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC,
PDO_SQLSRV_ERROR_INVALID_STMT_ATTR,
PDO_SQLSRV_ERROR_INVALID_ENCODING,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM,
PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED,
PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR,
PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR,
PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR,
PDO_SQLSRV_ERROR_INVALID_STMT_OPTION,
PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE,
PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED,
PDO_SQLSRV_ERROR_PARAM_PARSE,
PDO_SQLSRV_ERROR_LAST_INSERT_ID,
PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA,
PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING,
PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION,
PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE,
PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY,
PDO_SQLSRV_ERROR_INVALID_DSN_STRING,
PDO_SQLSRV_ERROR_INVALID_DSN_KEY,
PDO_SQLSRV_ERROR_INVALID_DSN_VALUE,
PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED,
PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY,
PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING,
SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN,
PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE,
PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY,
PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX,
PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE,
};
extern pdo_error PDO_ERRORS[];
#define THROW_PDO_ERROR( ctx, custom, ... ) \
call_error_handler( ctx, custom TSRMLS_CC, false, __VA_ARGS__ ); \
throw pdo::PDOException();
namespace pdo {
// an error which occurred in our PDO driver, NOT an exception thrown by PDO
struct PDOException : public core::CoreException {
PDOException() : CoreException()
{
}
};
} // namespace pdo
// called pdo_parse_params in php_pdo_driver.h
// we renamed it for 2 reasons: 1) we can't have the same name since it would conflict with our dynamic linking, and
// 2) this is a more precise name
extern int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, int inquery_len,
char **outquery, int *outquery_len TSRMLS_DC);
// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro
void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args );
#endif /* PDO_SQLSRV_H */

View file

@ -1,1303 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_stmt.cpp
//
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "pdo_sqlsrv.h"
// *** internal variables and constants ***
namespace {
// Maps to the list of PDO::FETCH_ORI_* constants
SQLSMALLINT odbc_fetch_orientation[] =
{
SQL_FETCH_NEXT, // PDO_FETCH_ORI_NEXT
SQL_FETCH_PRIOR, // PDO_FETCH_ORI_PRIOR
SQL_FETCH_FIRST, // PDO_FETCH_ORI_FIRST
SQL_FETCH_LAST, // PDO_FETCH_ORI_LAST
SQL_FETCH_ABSOLUTE, // PDO_FETCH_ORI_ABS
SQL_FETCH_RELATIVE // PDO_FETCH_ORI_REL
};
// max length of a field type
const int SQL_SERVER_IDENT_SIZE_MAX = 128;
inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori (enum pdo_fetch_orientation ori)
{
SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range." );
OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" );
return odbc_fetch_orientation[ori];
}
// Returns SQLSRV data type for a given PDO type. See pdo_param_type
// for list of supported pdo types.
SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( sqlsrv_stmt* driver_stmt, enum pdo_param_type pdo_type TSRMLS_DC )
{
switch( pdo_type ) {
case PDO_PARAM_BOOL:
case PDO_PARAM_INT:
return SQLSRV_PHPTYPE_INT;
case PDO_PARAM_STR:
return SQLSRV_PHPTYPE_STRING;
case PDO_PARAM_NULL:
return SQLSRV_PHPTYPE_NULL;
case PDO_PARAM_LOB:
// TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented.
return SQLSRV_PHPTYPE_STRING;
case PDO_PARAM_STMT:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
break;
default:
DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" );
}
return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning
}
// Returns a pdo type for a given SQL type. See pdo_param_type
// for list of supported pdo types.
inline pdo_param_type sql_type_to_pdo_type( SQLSMALLINT sql_type )
{
pdo_param_type return_type = PDO_PARAM_STR;
switch( sql_type ) {
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_BIGINT:
case SQL_BINARY:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_DOUBLE:
case SQL_FLOAT:
case SQL_GUID:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_NUMERIC:
case SQL_REAL:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_UDT:
case SQL_SS_VARIANT:
case SQL_SS_XML:
case SQL_TYPE_DATE:
case SQL_TYPE_TIMESTAMP:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WCHAR:
case SQL_WLONGVARCHAR:
case SQL_WVARCHAR:
return_type = PDO_PARAM_STR;
break;
default: {
DIE( "sql_type_to_pdo_type: Invalid SQL type provided." );
break;
}
}
return return_type;
}
// Calls core_sqlsrv_set_scrollable function to set cursor.
// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL.
void set_stmt_cursors( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
}
long pdo_cursor_type = Z_LVAL_P( value_z );
int odbc_cursor_type = -1;
switch( pdo_cursor_type ) {
case PDO_CURSOR_FWDONLY:
odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY;
break;
case PDO_CURSOR_SCROLL:
odbc_cursor_type = SQL_CURSOR_STATIC;
break;
default:
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
}
core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC );
}
void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
}
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE );
}
long odbc_cursor_type = Z_LVAL_P( value_z );
core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC );
return;
}
// Sets the statement encoding. Default encoding on the statement
// implies use the connection's encoding.
void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
// validate the value
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
}
long attr_value = Z_LVAL_P( value_z );
switch( attr_value ) {
// when the default encoding is applied to a statement, it means use the creating connection's encoding
case SQLSRV_ENCODING_DEFAULT:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_UTF8:
stmt->set_encoding( static_cast<SQLSRV_ENCODING>( attr_value ));
break;
default:
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
break;
}
}
// internal helper function to free meta data structures allocated
void meta_data_free( field_meta_data* meta )
{
sqlsrv_free( meta );
}
zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len )
{
zval* out_zval = NULL;
switch( sqlsrv_php_type ) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
ALLOC_INIT_ZVAL( out_zval );
if( *in_val == NULL ) {
ZVAL_NULL( out_zval );
}
else {
if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) {
ZVAL_LONG( out_zval, **( reinterpret_cast<long**>( in_val )));
}
else {
ZVAL_DOUBLE( out_zval, **( reinterpret_cast<double**>( in_val )));
}
}
if( *in_val ) {
sqlsrv_free( *in_val );
}
break;
}
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented
{
ALLOC_INIT_ZVAL( out_zval );
if( *in_val == NULL ) {
ZVAL_NULL( out_zval );
}
else {
ZVAL_STRINGL( out_zval, reinterpret_cast<char*>( *in_val ), field_len, 0 /*duplicate*/ );
}
break;
}
case SQLSRV_PHPTYPE_DATETIME:
DIE( "Unsupported php type" );
out_zval = ( reinterpret_cast<zval*>( *in_val ));
break;
case SQLSRV_PHPTYPE_NULL:
ALLOC_INIT_ZVAL( out_zval );
ZVAL_NULL( out_zval );
break;
default:
DIE( "Unknown php type" );
break;
}
return out_zval;
}
} // namespace
int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC);
int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC);
int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori,
long offset TSRMLS_DC);
int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt,
struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC);
int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC);
int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno,
char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC);
int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC);
int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, long attr, zval *return_value TSRMLS_DC);
int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC);
int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC);
int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC);
struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = {
pdo_sqlsrv_stmt_dtor,
pdo_sqlsrv_stmt_execute,
pdo_sqlsrv_stmt_fetch,
pdo_sqlsrv_stmt_describe_col,
pdo_sqlsrv_stmt_get_col_data,
pdo_sqlsrv_stmt_param_hook,
pdo_sqlsrv_stmt_set_attr,
pdo_sqlsrv_stmt_get_attr,
pdo_sqlsrv_stmt_get_col_meta,
pdo_sqlsrv_stmt_next_rowset,
pdo_sqlsrv_stmt_close_cursor
};
void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
set_stmt_cursors( stmt, value_z TSRMLS_CC );
}
void stmt_option_encoding:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
set_stmt_encoding( stmt, value_z TSRMLS_CC );
}
void stmt_option_direct_query:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false;
}
void stmt_option_cursor_scroll_type:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC );
}
void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
pdo_stmt_t *pdo_stmt = static_cast<pdo_stmt_t*>( stmt->driver() );
pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL;
}
// log a function entry point
#define PDO_LOG_STMT_ENTRY \
{ \
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data ); \
driver_stmt->set_func( __FUNCTION__ ); \
LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \
}
// PDO SQLSRV statement destructor
pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void )
{
std::for_each( current_meta_data.begin(), current_meta_data.end(), meta_data_free );
current_meta_data.clear();
if( bound_column_param_types ) {
sqlsrv_free( bound_column_param_types );
bound_column_param_types = NULL;
}
if( direct_query_subst_string ) {
// we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme
// and we use estrdup to allocate this string, which uses emalloc
efree( reinterpret_cast<void*>( const_cast<char*>( direct_query_subst_string )));
}
}
// pdo_sqlsrv_stmt_close_cursor
// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor.
// Parameters:
// *stmt - Pointer to current statement
// Return:
// Returns 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" );
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" );
// to "close the cursor" means we make the statement ready for execution again. To do this, we
// skip all the result sets on the current statement.
while( driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt TSRMLS_CC );
}
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding to the next result set." );
}
return 1;
}
// pdo_sqlsrv_stmt_describe_col
// Gets the metadata for a column based on the column number.
// Calls the core_sqlsrv_field_metadata function present in the core layer.
// Parameters:
// *stmt - pointer to current statement
// colno - Index of the column which requires description.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." );
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
try {
core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data ), colno TSRMLS_CC );
}
catch( core::CoreException& ) {
return 0;
}
catch(...) {
DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." );
}
pdo_column_data* column_data = &(stmt->columns[colno]);
SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" );
// Set the name
column_data->name = reinterpret_cast<char*>( core_meta_data->field_name.get());
core_meta_data->field_name.transferred();
// Set the namelen
column_data->namelen = core_meta_data->field_name_len;
// Set the maxlen
column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size;
// Set the precision
column_data->precision = core_meta_data->field_scale;
// Set the param_type
column_data->param_type = PDO_PARAM_ZVAL;
// store the field data for use by pdo_sqlsrv_stmt_get_col_data
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" );
driver_stmt->current_meta_data.push_back( core_meta_data.get() );
SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" );
core_meta_data.transferred();
return 1;
}
// pdo_sqlsrv_stmt_dtor
// Maps to PDOStatement::__destruct. Destructor for the PDO Statement.
// Parameters:
// *stmt - pointer to current statement
// Return:
// 1 for success.
int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
{
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" );
// if a PDO statement didn't complete preparation, its driver_data can be NULL
if( driver_stmt == NULL ) {
return 1;
}
driver_stmt->~sqlsrv_stmt();
sqlsrv_free( driver_stmt );
stmt->driver_data = NULL;
return 1;
}
// pdo_sqlsrv_stmt_execute
// Maps to PDOStatement::Execute. Executes the prepared statement.
// Parameters:
// *stmt - pointer to the current statement.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" );
// prepare for execution by flushing anything remaining in the result set if it wasn't already
// done before binding parameters
if( driver_stmt->executed && !driver_stmt->past_next_result_end ) {
while( driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false );
}
}
const char* query = NULL;
unsigned int query_len = 0;
// if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here
if( driver_stmt->direct_query ) {
query = driver_stmt->direct_query_subst_string;
query_len = driver_stmt->direct_query_subst_string_len;
}
// if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the
// subtituted query provided by PDO
if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
query = stmt->active_query_string;
query_len = stmt->active_query_stringlen;
}
core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len );
stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC );
// return the row count regardless if there are any rows or not
stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC );
// workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with
// the following sequence:
// 1) Prepare and execute a statement (that has some results with it)
// 2) call PDOStatement::nextRowset until there are no more results
// 3) execute the statement again
// 4) call PDOStatement::getColumnMeta
// It crashes from what I can tell because there is no metadata because there was no call to
// pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to
// PDO::execute. My guess is that because stmt->executed is true, it optimizes away a necessary call to
// pdo_sqlsrv_stmt_describe_col. By setting the stmt->executed flag to 0, this call is not optimized away
// and the crash disappears.
if( stmt->columns == NULL ) {
stmt->executed = 0;
}
}
catch( core::CoreException& /*e*/ ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." );
}
// success
return 1;
}
// pdo_sqlsrv_stmt_fetch
// Maps to PDOStatement::fetch
// Move the cursor to the record indicated. If the cursor is moved off the end,
// or before the beginning if a scrollable cursor is created, then FAILURE is returned.
// Parameters:
// *stmt - pointer to current statement for which the cursor should be moved.
// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants
// offset - For orientations that use it, offset to move to.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori,
long offset TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" );
// set the types for bound columns to zval so that PDO does no conversion when the value
// is returned by pdo_sqlsrv_get_col_data. Remember the types that were bound by the user
// and use it to manually convert data types
if( stmt->bound_columns ) {
pdo_bound_param_data* bind_data = NULL;
if( !driver_stmt->bound_column_param_types ) {
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 ));
std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
PDO_PARAM_ZVAL );
}
for( long i = 0; i < stmt->column_count; ++i ) {
if( zend_hash_index_find( stmt->bound_columns, i, (void**) &bind_data ) == FAILURE &&
zend_hash_find( stmt->bound_columns, stmt->columns[ i ].name, stmt->columns[ i ].namelen,
(void**) &bind_data ) == FAILURE ) {
driver_stmt->bound_column_param_types[ i ] = PDO_PARAM_ZVAL;
continue;
}
if( bind_data->param_type != PDO_PARAM_ZVAL ) {
driver_stmt->bound_column_param_types[ i ] = bind_data->param_type;
bind_data->param_type = PDO_PARAM_ZVAL;
}
}
}
SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori );
bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC );
// support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the
// pdo_stmt_t::row_count member
if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) {
stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC );
// a row_count of -1 means no rows, but we change it to 0
if( stmt->row_count == -1 ) {
stmt->row_count = 0;
}
}
// if no data was returned, then return false so data isn't retrieved
if( !data ) {
return 0;
}
return 1;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred.");
}
}
// pdo_sqlsrv_stmt_get_col_data
// Called by the set of PDO Fetch functions.
// Retrieves a single column. PDO driver manager is responsible for freeing the
// returned buffer. Because PDO can request fields out of order and ODBC does not
// support out of order field requests, this function should also cache fields.
// Parameters:
// stmt - Statement to retrive the column for.
// colno - Index of the column that needs to be retrieved. Starts with 0.
// ptr - Returns the buffer containing the column data.
// len - Length of the buffer returned.
// caller_frees - Flag to let the PDO driver manager know that it is responsible for
// freeing the memory.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno,
char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null" );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null" );
CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX ) {
return 0;
}
// Let PDO free the memory after use.
*caller_frees = 1;
// set the metadata for the current column
pdo_column_data* column_data = &(stmt->columns[colno]);
// translate the pdo type to a type the core layer understands
sqlsrv_phptype sqlsrv_php_type;
SQLSRV_ASSERT( colno >= 0 && colno < static_cast<int>( driver_stmt->current_meta_data.size()),
"Invalid column number in pdo_sqlsrv_stmt_get_col_data" );
sqlsrv_php_type = driver_stmt->sql_type_to_php_type( driver_stmt->current_meta_data[ colno ]->field_type,
driver_stmt->current_meta_data[ colno ]->field_size, true );
// set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding();
// if a column is bound to a type different than the column type, figure out a way to convert it to the
// type they want
if( stmt->bound_columns && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_ZVAL ) {
sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt,
driver_stmt->bound_column_param_types[ colno ]
TSRMLS_CC );
pdo_bound_param_data* bind_data = NULL;
int zr = zend_hash_index_find( stmt->bound_columns, colno, (void**) &bind_data );
if( bind_data != NULL && bind_data->driver_params != NULL ) {
CHECK_CUSTOM_ERROR( Z_TYPE_P( bind_data->driver_params ) != IS_LONG, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1 ) {
throw pdo::PDOException();
}
CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR
&& driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt,
PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) {
throw pdo::PDOException();
}
sqlsrv_php_type.typeinfo.encoding = Z_LVAL_P( bind_data->driver_params );
switch( sqlsrv_php_type.typeinfo.encoding ) {
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_UTF8:
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno );
break;
}
}
}
SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;
core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, reinterpret_cast<void**>( ptr ),
reinterpret_cast<SQLLEN*>( len ), true, &sqlsrv_phptype_out TSRMLS_CC );
zval** zval_ptr = reinterpret_cast<zval**>( sqlsrv_malloc( sizeof( zval* )));
*zval_ptr = reinterpret_cast<zval*>( convert_to_zval( sqlsrv_phptype_out, reinterpret_cast<void**>( ptr ), *len ));
*ptr = reinterpret_cast<char*>( zval_ptr );
*len = sizeof( zval );
return 1;
}
catch ( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred.");
}
}
// pdo_sqlsrv_stmt_set_attr
// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement.
// Parameters:
// stmt - Current statement on which the attribute should be set.
// attr - Represents any valid set of attribute constants supported by this driver.
// val - Attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
sqlsrv_stmt* driver_stmt = static_cast<sqlsrv_stmt*>( stmt->driver_data );
try {
switch( attr ) {
case SQLSRV_ATTR_DIRECT_QUERY:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY );
break;
case SQLSRV_ATTR_ENCODING:
set_stmt_encoding( driver_stmt, val TSRMLS_CC );
break;
case PDO_ATTR_CURSOR:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY );
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
core_sqlsrv_set_query_timeout( driver_stmt, val TSRMLS_CC );
break;
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY );
break;
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC );
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
}
}
catch( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred.");
}
return 1;
}
// pdo_sqlsrv_stmt_get_attr
// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement.
// Parameters:
// stmt - Current statement for which the attribute value is requested.
// attr - Represents any valid set of attribute constants supported by this driver.
// return_value - Attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, long attr, zval *return_value TSRMLS_DC )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" );
try {
switch( attr ) {
case SQLSRV_ATTR_DIRECT_QUERY:
{
ZVAL_BOOL( return_value, driver_stmt->direct_query );
break;
}
case SQLSRV_ATTR_ENCODING:
{
ZVAL_LONG( return_value, driver_stmt->encoding() );
break;
}
case PDO_ATTR_CURSOR:
{
ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ?
PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY ));
break;
}
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
{
ZVAL_LONG( return_value, driver_stmt->cursor_type );
break;
}
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
{
ZVAL_LONG( return_value, driver_stmt->buffered_query_limit );
break;
}
case SQLSRV_ATTR_QUERY_TIMEOUT:
{
ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout ));
break;
}
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
}
}
catch( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred.");
}
return 1;
}
// pdo_sqlsrv_stmt_get_col_meta
// Maps to PDOStatement::getColumnMeta. Return extra metadata.
// Though we don't return any extra metadata, PDO relies on us to
// create the associative array that holds the standard information,
// so we create one and return it for PDO's use.
// Parameters:
// stmt - Current statement.
// colno - The index of the field for which to return the metadata.
// return_value - zval* consisting of the metadata.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." );
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
sqlsrv_stmt* driver_stmt = static_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( colno >= 0 && colno < SHRT_MAX, "pdo_sqlsrv_stmt_get_col_meta: tried to overflow a short" );
core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC );
// initialize the array to nothing, as PDO requires us to create it
core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC );
// add the following fields: flags, native_type, driver:decl_type, table
add_assoc_long( return_value, "flags", 0 );
// get the name of the data type
char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ];
SQLSMALLINT out_buff_len;
SQLLEN not_used;
core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name,
sizeof( field_type_name ), &out_buff_len, &not_used TSRMLS_CC );
add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name, 1 );
// get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when
// given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only
// say string, since the length is given in another field of the metadata array.
long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type );
switch( pdo_type ) {
case PDO_PARAM_STR:
add_assoc_string( return_value, "native_type", "string", 1 );
break;
default:
DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" );
break;
}
// add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec
char table_name[ SQL_SERVER_IDENT_SIZE_MAX ];
SQLLEN field_type_num;
core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX,
&out_buff_len, &field_type_num TSRMLS_CC );
add_assoc_string( return_value, "table", table_name, 1 );
if( stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) {
add_assoc_long( return_value, "pdo_type", pdo_type );
}
// this will ensure that the field_name field, which is an auto pointer gets freed.
(*core_meta_data).~field_meta_data();
}
catch( core::CoreException& ) {
return 0;
}
catch(...) {
DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." );
}
return 1;
}
// pdo_sqlsrv_stmt_next_rowset
// Maps to PDOStatement::nextRowset.
// Move the cursor to the beginning of the next rowset in a multi-rowset result.
// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data.
// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data.
// If another result set is available, this function returns 1. Otherwise it returns 0.
// Parameters:
// stmt - PDOStatement object containing the result set.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" );
core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) TSRMLS_CC );
// clear the current meta data since the new result will generate new meta data
std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free );
driver_stmt->current_meta_data.clear();
// if there are no more result sets, return that it failed.
if( driver_stmt->past_next_result_end == true ) {
return 0;
}
stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC );
// return the row count regardless if there are any rows or not
stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC );
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." );
}
return 1;
}
// pdo_sqlsrv_stmt_param_hook
// Maps to PDOStatement::bindColumn.
// Called by PDO driver manager to bind a parameter or column.
// This function pulls several duties for binding parameters and columns.
// It takes an event as a parameter that explains what the function should do on
// behalf of a parameter or column. We only use one of these events,
// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return.
// Paramters:
// stmt - PDO Statement object to bind a parameter.
// param - paramter to bind.
// event_type - Event to bind a parameter
// Return:
// Returns 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt,
struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC)
{
PDO_RESET_STMT_ERROR;
try {
switch( event_type ) {
// since the param isn't reliable, we don't do anything here
case PDO_PARAM_EVT_ALLOC:
break;
case PDO_PARAM_EVT_FREE:
break;
// bind the parameter in the core layer
case PDO_PARAM_EVT_EXEC_PRE:
{
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
// skip column bindings
if( !param->is_param ) {
break;
}
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" );
// prepare for binding parameters by flushing anything remaining in the result set
if( driver_stmt->executed && !driver_stmt->past_next_result_end ) {
while( driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false );
}
}
int direction = SQL_PARAM_INPUT;
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
SQLSMALLINT decimal_digits = 0;
// determine the direction of the parameter. By default it's input, but if the user specifies a size
// that means they want output, and if they include the flag, then it's input/output.
// It's invalid to specify the input/output flag but not specify a length
CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0),
driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
throw pdo::PDOException();
}
if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
direction = SQL_PARAM_INPUT_OUTPUT;
}
else {
direction = SQL_PARAM_OUTPUT;
}
}
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
int pdo_type = param->param_type;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
switch( pdo_type & ~PDO_PARAM_INPUT_OUTPUT ) {
case PDO_PARAM_BOOL:
case PDO_PARAM_INT:
php_out_type = SQLSRV_PHPTYPE_INT;
break;
case PDO_PARAM_STR:
php_out_type = SQLSRV_PHPTYPE_STRING;
break;
// when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is
// since the core layer keys off the zval type, we substitute a null for what they gave us
case PDO_PARAM_NULL:
{
zval* null_zval;
php_out_type = SQLSRV_PHPTYPE_NULL;
MAKE_STD_ZVAL( null_zval );
ZVAL_NULL( null_zval );
zval_ptr_dtor( &param->parameter );
param->parameter = null_zval;
break;
}
case PDO_PARAM_LOB:
php_out_type = SQLSRV_PHPTYPE_STREAM;
break;
case PDO_PARAM_STMT:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
break;
default:
SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." );
break;
}
// set the column size parameter for bind_param if we are expecting something back
if( direction != SQL_PARAM_INPUT ) {
switch( php_out_type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_STREAM:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE );
break;
case SQLSRV_PHPTYPE_INT:
column_size = SQLSRV_UNKNOWN_SIZE;
break;
case SQLSRV_PHPTYPE_STRING:
{
CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) {
throw pdo::PDOException();
}
column_size = param->max_value_len;
break;
}
default:
SQLSRV_ASSERT( false, "Invalid PHP type for output parameter. Should have been caught already." );
break;
}
}
// block all objects from being bound as input or input/output parameters since there is a
// weird case:
// $obj = date_create();
// $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR
// that succeeds since the core layer implements DateTime object handling for the sqlsrv
// 2.0 driver. To be consistent and avoid surprises of one object type working and others
// not, we block all objects here.
CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE_P( param->parameter ) == IS_OBJECT,
driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
throw pdo::PDOException();
}
// the encoding by default is that set on the statement
SQLSRV_ENCODING encoding = driver_stmt->encoding();
// if the statement's encoding is the default, then use the one on the connection
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = driver_stmt->conn->encoding();
}
// if the user provided an encoding, use it instead
if( param->driver_params != NULL ) {
CHECK_CUSTOM_ERROR( Z_TYPE_P( param->driver_params ) != IS_LONG, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
throw pdo::PDOException();
}
CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) {
throw pdo::PDOException();
}
encoding = static_cast<SQLSRV_ENCODING>( Z_LVAL_P( param->driver_params ));
switch( encoding ) {
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_UTF8:
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
param->paramno + 1 );
break;
}
}
// and bind the parameter
core_sqlsrv_bind_param( driver_stmt, param->paramno, direction, param->parameter, php_out_type, encoding,
sql_type, column_size, decimal_digits TSRMLS_CC );
}
break;
// undo any work done by the core layer after the statement is executed
case PDO_PARAM_EVT_EXEC_POST:
{
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
// skip column bindings
if( !param->is_param ) {
break;
}
core_sqlsrv_post_param( reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data ), param->paramno,
param->parameter TSRMLS_CC );
}
break;
case PDO_PARAM_EVT_FETCH_PRE:
break;
case PDO_PARAM_EVT_FETCH_POST:
break;
case PDO_PARAM_EVT_NORMALIZE:
break;
default:
DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" );
break;
}
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" );
}
return 1;
}
// Returns a sqlsrv_phptype for a given SQL Server data type.
sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream )
{
sqlsrv_phptype sqlsrv_phptype;
int local_encoding = this->encoding();
// if the encoding on the connection changed
if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) {
local_encoding = conn->encoding();
SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID,
"Invalid encoding on the connection. Must not be invalid or default." );
}
switch( sql_type ) {
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_FLOAT:
case SQL_REAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = local_encoding;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
break;
default:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
break;
}
return sqlsrv_phptype;
}

View file

@ -1,611 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_util.cpp
//
// Contents: Utility functions used by both connection or statement functions
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "pdo_sqlsrv.h"
#include "zend_exceptions.h"
// *** internal constants ***
namespace {
const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nMError Message: %3!s!\n";
const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s";
char EXCEPTION_PROPERTY_MSG[] = "message";
char EXCEPTION_PROPERTY_CODE[] = "code";
char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo";
const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative
// buffer used to hold a formatted log message prior to actually logging it.
const int LOG_MSG_SIZE = 2048;
char log_msg[ LOG_MSG_SIZE ];
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// build the object and throw the PDO exception
void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC );
}
// pdo driver error messages
// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0
pdo_error PDO_ERRORS[] = {
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver 11 for SQL Server "
"for %1!s!: "
"http://go.microsoft.com/fwlink/?LinkId=163712", -1, true }
},
{
SQLSRV_ERROR_ZEND_HASH,
{ IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false }
},
{
PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
{ IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams "
"cannot be specified as output parameters.", -3, false }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point "
"numbers, strings, and streams may be used as parameters.", -4, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
{ IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true }
},
{
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true }
},
{
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true }
},
{
SQLSRV_ERROR_ZEND_STREAM,
{ IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false }
},
{
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true }
},
{
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
{ IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false }
},
{
SQLSRV_ERROR_FETCH_PAST_END,
{ IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, "
"no more data may be retrieved.", -13, false }
},
{
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
{ IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false }
},
{
SQLSRV_ERROR_NO_FIELDS,
{ IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false }
},
{
SQLSRV_ERROR_FETCH_NOT_CALLED,
{ IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch "
"functions was called.", -16, false }
},
{
SQLSRV_ERROR_NO_DATA,
{ IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true }
},
{
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true }
},
{
SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED,
{ IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false }
},
{
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false }
},
{
SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be"
" escaped with another right brace (}}).", -21, false }
},
{
SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN,
{ IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces "
"must be escaped with another right brace (}}).", -22, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true }
},
{
SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
{ IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of "
"key/value pairs.", -25, false }
},
{
SQLSRV_ERROR_INVALID_CONNECTION_KEY,
{ IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false }
},
{
SQLSRV_ERROR_INVALID_TYPE,
{ IMSSP, (SQLCHAR*) "Invalid type.", -27, false }
},
{
PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX,
{IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false }
},
{
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_KEY,
{ IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true }
},
{
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
{ IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DBH_ATTR,
{ IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false }
},
{
PDO_SQLSRV_ERROR_INVALID_STMT_ATTR,
{ IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false }
},
{
PDO_SQLSRV_ERROR_INVALID_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM,
{ IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants "
"such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false }
},
{
PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false }
},
{
PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR,
{ IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false }
},
{
PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR,
{ IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false }
},
{
PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR,
{ IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DSN_STRING,
{IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DSN_KEY,
{ IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true }
},
{
PDO_SQLSRV_ERROR_INVALID_STMT_OPTION,
{ IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false }
},
{
PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE,
{ IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or "
"PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false }
},
{
PDO_SQLSRV_ERROR_PARAM_PARSE,
{ IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false }
},
{
PDO_SQLSRV_ERROR_LAST_INSERT_ID,
{ IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false }
},
{
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true }
},
{
PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA,
{ IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only "
"encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true }
},
{
PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING,
{ IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types "
"can take an encoding option.", -49, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING,
{ IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE,
{ IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an "
"encoding option.", -51, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
{ IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true }
},
{
PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY,
{ IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the "
"$driver_options array of PDO::prepare.", -53, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
{ IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true }
},
{
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
{ IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output "
"parameter %1!d!.", -55, true }
},
{
PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION,
{ IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.",
-56, true }
},
{
PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE,
{ IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an "
"explicit length.", -57, true }
},
{
PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED,
{ IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false }
},
{
/* The stream related errors are not currently used in PDO, but the core layer can throw the stream related
errors so having a mapping here */
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
{ IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using "
"streams.", -59, false}
},
{
SQLSRV_ERROR_STREAM_CREATE,
{ IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false }
},
{
SQLSRV_ERROR_MARS_OFF,
{ IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. "
"To make the connection available for other queries, either fetch all results or cancel or free the statement. "
"For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false }
},
{
SQLSRV_ERROR_FIELD_INDEX_ERROR,
{ IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its "
"index is less than the index of a field that has already been retrieved (%2!d!).", -62, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DSN_VALUE,
{ IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true }
},
{
PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED,
{ IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false }
},
{
PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY,
{ IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false }
},
{
PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING,
{ IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .",
-66, true }
},
{
PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE,
{ IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.",
-67, true }
},
{
PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY,
{ IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of "
"PDO::prepare.", -68, false }
},
{
PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE,
{ IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to "
"PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false }
},
{
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
{ IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not "
"positive. Only positive numbers are valid for this attribute.", -70, false }
},
{
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true }
},
{ -1, {} }
};
// Returns a sqlsrv_error for a given error code.
sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) {
sqlsrv_error_const *error_message = NULL;
int zr = zend_hash_index_find( g_pdo_errors_ht, sqlsrv_error_code, reinterpret_cast<void**>( &error_message ));
if( zr == FAILURE ) {
DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code );
}
SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null");
return error_message;
}
// PDO error handler for the environment context.
bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
va_list* print_args )
{
SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" );
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>( ctx.driver());
SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" );
sqlsrv_error_auto_ptr error;
if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args );
}
else {
bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC );
SQLSRV_ASSERT( err == true, "No ODBC error was found" );
}
strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast<const char*>( error->sqlstate ));
switch( dbh->error_mode ) {
case PDO_ERRMODE_EXCEPTION:
if( !warning ) {
pdo_sqlsrv_throw_exception( error TSRMLS_CC );
}
ctx.set_last_error( error );
break;
default:
DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode );
break;
}
// we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count
// return error ignored = true for warnings.
return ( warning ? true : false );
}
// pdo error handler for the dbh context.
bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
va_list* print_args )
{
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>( ctx.driver());
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" );
sqlsrv_error_auto_ptr error;
if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args );
}
else {
bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC );
SQLSRV_ASSERT( err == true, "No ODBC error was found" );
}
SQLSRV_STATIC_ASSERT( sizeof( error->sqlstate ) <= sizeof( dbh->error_code ));
strcpy_s( dbh->error_code, sizeof( dbh->error_code ), reinterpret_cast<const char*>( error->sqlstate ));
switch( dbh->error_mode ) {
case PDO_ERRMODE_EXCEPTION:
if( !warning ) {
pdo_sqlsrv_throw_exception( error TSRMLS_CC );
}
ctx.set_last_error( error );
break;
case PDO_ERRMODE_WARNING:
if( !warning ) {
unsigned int msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
+ MAX_DIGITS + 1;
sqlsrv_malloc_auto_ptr<char> msg;
msg = static_cast<char*>( sqlsrv_malloc( msg_len ));
core_sqlsrv_format_message( msg, msg_len, WARNING_TEMPLATE, error->sqlstate, error->native_code,
error->native_message );
php_error( E_WARNING, msg );
sqlsrv_free( msg );
}
ctx.set_last_error( error );
break;
case PDO_ERRMODE_SILENT:
ctx.set_last_error( error );
break;
default:
DIE( "Unknown error mode. %1!d!", dbh->error_mode );
break;
}
// return error ignored = true for warnings.
return ( warning ? true : false );
}
// PDO error handler for the statement context.
bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
va_list* print_args )
{
pdo_stmt_t* pdo_stmt = reinterpret_cast<pdo_stmt_t*>( ctx.driver());
SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" );
sqlsrv_error_auto_ptr error;
if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args );
}
else {
bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC );
SQLSRV_ASSERT( err == true, "No ODBC error was found" );
}
SQLSRV_STATIC_ASSERT( sizeof( error->sqlstate ) <= sizeof( pdo_stmt->error_code ));
strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast<const char*>( error->sqlstate ));
switch( pdo_stmt->dbh->error_mode ) {
case PDO_ERRMODE_EXCEPTION:
if( !warning ) {
pdo_sqlsrv_throw_exception( error TSRMLS_CC );
}
ctx.set_last_error( error );
break;
case PDO_ERRMODE_WARNING:
if( !warning ) {
unsigned int msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
+ MAX_DIGITS + 1;
sqlsrv_malloc_auto_ptr<char> msg;
msg = static_cast<char*>( sqlsrv_malloc( msg_len ));
core_sqlsrv_format_message( msg, msg_len, WARNING_TEMPLATE, error->sqlstate, error->native_code,
error->native_message );
php_error( E_WARNING, msg );
sqlsrv_free( msg );
}
ctx.set_last_error( error );
break;
case PDO_ERRMODE_SILENT:
ctx.set_last_error( error );
break;
default:
DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode );
break;
}
// return error ignored = true for warnings.
return ( warning ? true : false );
}
// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements:
// 0, native code
// 1, native message
// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP')
void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval )
{
if( last_error ) {
// SQLSTATE is already present in the zval.
add_next_index_long( pdo_zval, last_error->native_code );
add_next_index_string( pdo_zval, reinterpret_cast<char*>( last_error->native_message ), 1 /*dup*/ );
}
else {
add_next_index_null( pdo_zval ); /* native code */
add_next_index_null( pdo_zval ); /* native message */
}
}
// Formats the error message and writes to the php error log.
void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args )
{
if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) {
return;
}
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args );
// if an error occurs for FormatMessage, we just output an internal error occurred.
if( rc == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg ));
std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg );
}
php_log_err( log_msg TSRMLS_CC );
}
namespace {
void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC )
{
zval_auto_ptr ex_obj;
MAKE_STD_ZVAL( ex_obj );
zend_class_entry* ex_class = pdo_get_exception_class();
int zr = object_init_ex( ex_obj, ex_class );
SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" );
sqlsrv_malloc_auto_ptr<char> ex_msg;
size_t ex_msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE +
12 + 1; // 12 = "SQLSTATE[]: "
ex_msg = reinterpret_cast<char*>( sqlsrv_malloc( ex_msg_len ));
snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message );
zend_update_property_string( ex_class, ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1,
ex_msg TSRMLS_CC );
zend_update_property_string( ex_class, ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1,
reinterpret_cast<char*>( error->sqlstate ) TSRMLS_CC );
zval_auto_ptr ex_error_info;
MAKE_STD_ZVAL( ex_error_info );
array_init( ex_error_info );
add_next_index_string( ex_error_info, reinterpret_cast<char*>( error->sqlstate ), 1 /* dup */ );
add_next_index_long( ex_error_info, error->native_code );
add_next_index_string( ex_error_info, reinterpret_cast<char*>( error->native_message ), 1 /* dup */ );
zend_update_property( ex_class, ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1,
ex_error_info TSRMLS_CC );
zend_throw_exception_object( ex_obj TSRMLS_CC );
ex_msg.transferred();
ex_obj.transferred();
}
}

View file

@ -1,83 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: template.rc
//
// Contents: Version resource
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#ifdef APSTUDIO_INVOKED
# error dont edit with MSVC
#endif
#include "winresrc.h"
#include "main/php_version.h"
#include "version.h"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifndef THANKS_GUYS
# define THANKS_GUYS ""
#endif
#ifdef WANT_LOGO
0 ICON win32\build\php.ico
#endif
#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build
#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel
#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build)
#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel)
//Version
VS_VERSION_INFO VERSIONINFO
FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION
PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. © 1997-2009 The PHP Group. All rights reserved.\0"
VALUE "CompanyName", "Microsoft Corp.\0"
VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0"
VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION)
VALUE "InternalName", FILE_NAME "\0"
VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0"
VALUE "OriginalFilename", FILE_NAME "\0"
VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0"
VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD)
VALUE "URL", "http://www.microsoft.com\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#ifdef MC_INCLUDE
#include MC_INCLUDE
#endif

View file

@ -1,27 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: version.h
// Contents: Version number constants
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#define VER_FILEVERSION_STR "3.2.0.0"
#define _FILEVERSION 3,2,0,0
#define SQLVERSION_MAJOR 3
#define SQLVERSION_MINOR 2
#define SQLVERSION_MMDD 0
#define SQLVERSION_REVISION 0

View file

@ -1 +0,0 @@
Microsoft Drivers 3.2.0 for PHP for SQL Server (SQLSRV driver)

View file

@ -1,38 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: config.w32
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no");
if( PHP_SQLSRV != "no" ) {
if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") &&
CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")) {
EXTENSION("sqlsrv", "conn.cpp init.cpp stmt.cpp util.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp" )
CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_SQLSRV_ODBC');
CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_SQLSRV_ODBC');
ADD_FLAG( 'LDFLAGS_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' );
ADD_FLAG( 'CFLAGS_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' );
ADD_FLAG( 'CFLAGS_SQLSRV', '/D _HAS_CPP0X=0' );
ADD_FLAG( 'CFLAGS_SQLSRV', '/EHsc' );
ADD_FLAG( 'CFLAGS_SQLSRV', '/GS' );
ADD_FLAG( 'CFLAGS_SQLSRV', '/Zi' );
}
}

View file

@ -1,1353 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: conn.cpp
//
// Contents: Routines that use connection handles
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
#include <psapi.h>
#include <windows.h>
#include <winver.h>
#include <string>
#include <sstream>
// *** internal variables and constants ***
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_CONN;
struct date_as_string_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
TSRMLS_C; // show as used to avoid a warning
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>( conn );
if( zend_is_true( value )) {
ss_conn->date_as_string = true;
}
else {
ss_conn->date_as_string = false;
}
}
};
struct conn_char_set_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
convert_to_string( value );
const char* encoding = Z_STRVAL_P( value );
unsigned int encoding_len = Z_STRLEN_P( value );
for( zend_hash_internal_pointer_reset( g_ss_encodings_ht );
zend_hash_has_more_elements( g_ss_encodings_ht ) == SUCCESS;
zend_hash_move_forward( g_ss_encodings_ht )) {
sqlsrv_encoding* ss_encoding;
core::sqlsrv_zend_hash_get_current_data( *conn, g_ss_encodings_ht, (void**) &ss_encoding TSRMLS_CC );
if( !strnicmp( encoding, ss_encoding->iana, encoding_len ) ) {
if( ss_encoding->not_for_connection ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
}
conn->set_encoding( static_cast<SQLSRV_ENCODING>( ss_encoding->code_page ));
return;
}
}
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
}
};
struct bool_conn_str_func {
static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC )
{
TSRMLS_C;
char const* val_str;
if( zend_is_true( value )) {
val_str = "yes";
}
else {
val_str = "no";
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
}
};
template <unsigned int Attr>
struct int_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( value )),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
template <unsigned int Attr>
struct bool_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( zend_is_true( value )),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
//// *** internal functions ***
void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC );
void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, __out char** uid, __out char** pwd,
__inout HashTable* ss_conn_options_ht TSRMLS_DC );
void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* ss_stmt_options_ht TSRMLS_DC );
void add_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len,
HashTable* options_ht, zval** data TSRMLS_DC );
void add_stmt_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, HashTable* options_ht, zval** data TSRMLS_DC );
int get_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, zval const* value_z TSRMLS_DC );
int get_stmt_option_key( char* key, unsigned int key_len TSRMLS_DC );
}
// constants for parameters used by process_params function(s)
int ss_sqlsrv_conn::descriptor;
char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn";
// connection specific parameter proccessing. Use the generic function specialised to return a connection
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
rsrc = process_params<ss_sqlsrv_conn>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, __VA_ARGS__ ); \
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
namespace SSStmtOptionNames {
const char QUERY_TIMEOUT[]= "QueryTimeout";
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
const char SCROLLABLE[] = "Scrollable";
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
}
namespace SSConnOptionNames {
// most of these strings are the same for both the sqlsrv_connect connection option
// and the name put into the connection string. MARS is the only one that's different.
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char DateAsString[] = "ReturnDatesAsStrings";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_Option[] = "MultipleActiveResultSets";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
const char PWD[] = "PWD";
const char QuotedId[] = "QuotedId";
const char TraceFile[] = "TraceFile";
const char TraceOn[] = "TraceOn";
const char TrustServerCertificate[] = "TrustServerCertificate";
const char TransactionIsolation[] = "TransactionIsolation";
const char UID[] = "UID";
const char WSID[] = "WSID";
}
enum SS_CONN_OPTIONS {
SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
};
//List of all statement options supported by this driver
const stmt_option SS_STMT_OPTS[] = {
{
SSStmtOptionNames::QUERY_TIMEOUT,
sizeof( SSStmtOptionNames::QUERY_TIMEOUT ),
SQLSRV_STMT_OPTION_QUERY_TIMEOUT,
new stmt_option_query_timeout
},
{
SSStmtOptionNames::SEND_STREAMS_AT_EXEC,
sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ),
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
new stmt_option_send_at_exec
},
{
SSStmtOptionNames::SCROLLABLE,
sizeof( SSStmtOptionNames::SCROLLABLE ),
SQLSRV_STMT_OPTION_SCROLLABLE,
new stmt_option_scrollable
},
{
SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE,
sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ),
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
new stmt_option_buffered_query_limit
},
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, NULL },
};
// List of all connection options supported by this driver.
const connection_option SS_CONN_OPTS[] = {
{
SSConnOptionNames::APP,
sizeof( SSConnOptionNames::APP ),
SQLSRV_CONN_OPTION_APP,
ODBCConnOptions::APP,
sizeof( ODBCConnOptions::APP ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::ApplicationIntent,
sizeof( SSConnOptionNames::ApplicationIntent ),
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
ODBCConnOptions::ApplicationIntent,
sizeof( ODBCConnOptions::ApplicationIntent ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::AttachDBFileName,
sizeof( SSConnOptionNames::AttachDBFileName ),
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
ODBCConnOptions::AttachDBFileName,
sizeof( ODBCConnOptions::AttachDBFileName ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::CharacterSet,
sizeof( SSConnOptionNames::CharacterSet ),
SQLSRV_CONN_OPTION_CHARACTERSET,
ODBCConnOptions::CharacterSet,
sizeof( ODBCConnOptions::CharacterSet ),
CONN_ATTR_STRING,
conn_char_set_func::func
},
{
SSConnOptionNames::ConnectionPooling,
sizeof( SSConnOptionNames::ConnectionPooling ),
SQLSRV_CONN_OPTION_CONN_POOLING,
ODBCConnOptions::ConnectionPooling,
sizeof( ODBCConnOptions::ConnectionPooling ),
CONN_ATTR_BOOL,
conn_null_func::func
},
{
SSConnOptionNames::Database,
sizeof( SSConnOptionNames::Database ),
SQLSRV_CONN_OPTION_DATABASE,
ODBCConnOptions::Database,
sizeof( ODBCConnOptions::Database ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::Encrypt,
sizeof( SSConnOptionNames::Encrypt ),
SQLSRV_CONN_OPTION_ENCRYPT,
ODBCConnOptions::Encrypt,
sizeof( ODBCConnOptions::Encrypt ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::Failover_Partner,
sizeof( SSConnOptionNames::Failover_Partner ),
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
ODBCConnOptions::Failover_Partner,
sizeof( ODBCConnOptions::Failover_Partner ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::LoginTimeout,
sizeof( SSConnOptionNames::LoginTimeout ),
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
ODBCConnOptions::LoginTimeout,
sizeof( ODBCConnOptions::LoginTimeout ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
},
{
SSConnOptionNames::MARS_Option,
sizeof( SSConnOptionNames::MARS_Option ),
SQLSRV_CONN_OPTION_MARS,
ODBCConnOptions::MARS_ODBC,
sizeof( ODBCConnOptions::MARS_ODBC ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::MultiSubnetFailover,
sizeof( SSConnOptionNames::MultiSubnetFailover ),
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
ODBCConnOptions::MultiSubnetFailover,
sizeof( ODBCConnOptions::MultiSubnetFailover ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::QuotedId,
sizeof( SSConnOptionNames::QuotedId ),
SQLSRV_CONN_OPTION_QUOTED_ID,
ODBCConnOptions::QuotedId,
sizeof( ODBCConnOptions::QuotedId ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::TraceFile,
sizeof( SSConnOptionNames::TraceFile ),
SQLSRV_CONN_OPTION_TRACE_FILE,
ODBCConnOptions::TraceFile,
sizeof( ODBCConnOptions::TraceFile ),
CONN_ATTR_STRING,
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
},
{
SSConnOptionNames::TraceOn,
sizeof( SSConnOptionNames::TraceOn ),
SQLSRV_CONN_OPTION_TRACE_ON,
ODBCConnOptions::TraceOn,
sizeof( ODBCConnOptions::TraceOn ),
CONN_ATTR_BOOL,
bool_conn_attr_func<SQL_ATTR_TRACE>::func
},
{
SSConnOptionNames::TransactionIsolation,
sizeof( SSConnOptionNames::TransactionIsolation ),
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
ODBCConnOptions::TransactionIsolation,
sizeof( ODBCConnOptions::TransactionIsolation ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_COPT_SS_TXN_ISOLATION>::func
},
{
SSConnOptionNames::TrustServerCertificate,
sizeof( SSConnOptionNames::TrustServerCertificate ),
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
ODBCConnOptions::TrustServerCertificate,
sizeof( ODBCConnOptions::TrustServerCertificate ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::WSID,
sizeof( SSConnOptionNames::WSID ),
SQLSRV_CONN_OPTION_WSID,
ODBCConnOptions::WSID,
sizeof( ODBCConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::DateAsString,
sizeof( SSConnOptionNames::DateAsString ),
SS_CONN_OPTION_DATE_AS_STRING,
SSConnOptionNames::DateAsString,
sizeof( SSConnOptionNames::DateAsString ),
CONN_ATTR_BOOL,
date_as_string_func::func
},
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
};
// sqlsrv_connect( string $serverName [, array $connectionInfo])
//
// Creates a connection resource and opens a connection. By default, the
// connection is attempted using Windows Authentication.
//
// Parameters
// $serverName: A string specifying the name of the server to which a connection
// is being established. An instance name (for example, "myServer\instanceName")
// or port number (for example, "myServer, 1521") can be included as part of
// this string. For a complete description of the options available for this
// parameter, see the Server keyword in the ODBC Driver Connection String
// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server.
//
// $connectionInfo [OPTIONAL]: An associative array that contains connection
// attributes (for example, array("Database" => "AdventureWorks")).
//
// Return Value
// A PHP connection resource. If a connection cannot be successfully created and
// opened, false is returned
PHP_FUNCTION ( sqlsrv_connect )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_connect" );
SET_FUNCTION_NAME( *g_henv_cp );
SET_FUNCTION_NAME( *g_henv_ncp );
reset_errors( TSRMLS_C );
const char* server = NULL;
zval* options_z = NULL;
char* uid = NULL;
char* pwd = NULL;
unsigned int server_len = 0;
// get the server name and connection options
int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z );
CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) {
RETURN_FALSE;
}
hash_auto_ptr ss_conn_options_ht;
hash_auto_ptr stmts;
ss_sqlsrv_conn* conn = NULL;
try {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_conn_options_ht );
core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, NULL /*hashfn*/,
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
// Either of g_henv_cp or g_henv_ncp can be used to propogate the error.
::validate_conn_options( *g_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC );
// call the core connect function
conn = static_cast<ss_sqlsrv_conn*>( core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, &core::allocate_conn<ss_sqlsrv_conn>,
server, uid, pwd, ss_conn_options_ht, ss_error_handler,
SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC ));
SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." );
// create a bunch of statements
ALLOC_HASHTABLE( stmts );
core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* hashfn */, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC );
// register the connection with the PHP runtime
ss::zend_register_resource( return_value, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC );
conn->stmts = stmts;
stmts.transferred();
}
catch( core::CoreException& ) {
if( conn != NULL ) {
conn->invalidate();
}
RETURN_FALSE;
}
catch( ... ) {
DIE("sqlsrv_connect: Unknown exception caught.");
}
}
// sqlsrv_begin_transaction( resource $conn )
//
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback
// or sqlsrv_commit.
//
// The SQLSRV driver is in auto-commit mode by default. This means that all
// queries are automatically committed upon success unless they have been
// designated as part of an explicit transaction by using
// sqlsrv_begin_transaction.
//
// If sqlsrv_begin_transaction is called after a transaction has already been
// initiated on the connection but not completed by calling either sqlsrv_commit
// or sqlsrv_rollback, the call returns false and an Already in Transaction
// error is added to the error collection.
//
// Parameters
// $conn: The connection with which the transaction is associated.
//
// Return Value
// A Boolean value: true if the transaction was successfully begun. Otherwise, false.
PHP_FUNCTION( sqlsrv_begin_transaction )
{
LOG_FUNCTION( "sqlsrv_begin_transaction" );
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if already in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) {
RETURN_FALSE;
}
try {
core_sqlsrv_begin_transaction( conn TSRMLS_CC );
conn->in_transaction = true;
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE("sqlsrv_begin_transaction: Unknown exception caught.");
}
}
// sqlsrv_close( resource $conn )
// Closes the specified connection and releases associated resources.
//
// Parameters
// $conn: The connection to be closed. Null is a valid value parameter for this
// parameter. This allows the function to be called multiple times in a
// script. For example, if you close a connection in an error condition and
// close it again at the end of the script, the second call to sqlsrv_close will
// return true because the first call to sqlsrv_close (in the error condition)
// sets the connection resource to null.
//
// Return Value
// The Boolean value true unless the function is called with an invalid
// parameter. If the function is called with an invalid parameter, false is
// returned.
PHP_FUNCTION( sqlsrv_close )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_close" );
zval* conn_r = NULL;
ss_sqlsrv_conn* conn = NULL;
sqlsrv_context_auto_ptr error_ctx;
full_mem_check(MEMCHECK_SILENT);
reset_errors( TSRMLS_C );
try {
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r ) == FAILURE ) {
// Check if it was a zval
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
// if sqlsrv_close was called on a non-existent connection than we just return success.
if( Z_TYPE_P( conn_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
conn = static_cast<ss_sqlsrv_conn*>( zend_fetch_resource( &conn_r TSRMLS_CC, -1, ss_sqlsrv_conn::resource_name, NULL, 1,
ss_sqlsrv_conn::descriptor ));
CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
SET_FUNCTION_NAME( *conn );
// cause any variables still holding a reference to this to be invalid so they cause
// an error when passed to a sqlsrv function. There's nothing we can do if the
// removal fails, so we just log it and move on.
int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( conn_r ));
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RESVAL_P( conn_r ));
}
ZVAL_NULL( conn_r );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_close: Unknown exception caught." );
}
}
void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC )
{
LOG_FUNCTION( "sqlsrv_conn_dtor" );
// get the structure
ss_sqlsrv_conn *conn = static_cast<ss_sqlsrv_conn*>( rsrc->ptr );
SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null");
SET_FUNCTION_NAME( *conn );
// close all statements associated with the connection.
sqlsrv_conn_close_stmts( conn TSRMLS_CC );
// close the connection itself.
core_sqlsrv_close( conn TSRMLS_CC );
rsrc->ptr = NULL;
}
// sqlsrv_commit( resource $conn )
//
// Commits the current transaction on the specified connection and returns the
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
// sqlsrv_commit.
// The SQLSRV driver is in auto-commit mode by
// default. This means that all queries are automatically committed upon success
// unless they have been designated as part of an explicit transaction by using
// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is
// not in an active transaction and that was initiated with
// sqlsrv_begin_transaction, the call returns false and a Not in Transaction
// error is added to the error collection.
//
// Parameters
// $conn: The connection on which the transaction is active.
//
// Return Value
// A Boolean value: true if the transaction was successfully committed. Otherwise, false.
PHP_FUNCTION( sqlsrv_commit )
{
LOG_FUNCTION( "sqlsrv_commit" );
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if not in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
RETURN_FALSE;
}
try {
conn->in_transaction = false;
core_sqlsrv_commit( conn TSRMLS_CC );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_commit: Unknown exception caught." );
}
}
// sqlsrv_rollback( resource $conn )
//
// Rolls back the current transaction on the specified connection and returns
// the connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
// sqlsrv_commit.
//
// The SQLSRV driver is in auto-commit mode by default. This
// means that all queries are automatically committed upon success unless they
// have been designated as part of an explicit transaction by using
// sqlsrv_begin_transaction.
//
// If sqlsrv_rollback is called on a connection that is not in an active
// transaction that was initiated with sqlsrv_begin_transaction, the call
// returns false and a Not in Transaction error is added to the error
// collection.
//
// Parameters
// $conn: The connection on which the transaction is active.
//
// Return Value
// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false.
PHP_FUNCTION( sqlsrv_rollback )
{
LOG_FUNCTION( "sqlsrv_rollback" );
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if not in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
RETURN_FALSE;
}
try {
conn->in_transaction = false;
core_sqlsrv_rollback( conn TSRMLS_CC );
RETURN_TRUE;
}
catch( core::CoreException& ){
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_rollback: Unknown exception caught." );
}
}
// sqlsrv_client_info
// Returns the ODBC driver's dll name, version and the ODBC version. Also returns
// the version of this extension.
// Parameters:
// $conn - The connection resource by which the client and server are connected.
PHP_FUNCTION( sqlsrv_client_info )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_client_info" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
try {
core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC );
// Add the sqlsrv driver's file version
core::sqlsrv_add_assoc_string( *conn, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/ TSRMLS_CC );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_client_info: Unknown exception caught." );
}
}
// sqlsrv_server_info( resource $conn )
//
// Returns information about the server.
//
// Parameters
// $conn: The connection resource by which the client and server are connected.
//
// Return Value
// An associative array with the following keys:
// CurrentDatabase
// The database currently being targeted.
// SQLServerVersion
// The version of SQL Server.
// SQLServerName
// The name of the server.
PHP_FUNCTION( sqlsrv_server_info )
{
try {
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_server_info" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_server_info: Unknown exception caught." );
}
}
// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]])
//
// Creates a statement resource associated with the specified connection. A statement
// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute.
// In between each execution, the values may be updated by changing the value of the
// variables bound. Output parameters cannot be relied upon to contain their results until
// all rows are processed.
//
// Parameters
// $conn: The connection resource associated with the created statement.
//
// $tsql: The Transact-SQL expression that corresponds to the created statement.
//
// $params [OPTIONAL]: An array of values that correspond to parameters in a
// parameterized query. Each parameter may be specified as:
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
// When given just a $value, the direction is default input, and phptype is the value
// given, with the sql type inferred from the php type.
//
// $options [OPTIONAL]: An associative array that sets query properties. The
// table below lists the supported keys and corresponding values:
// QueryTimeout
// Sets the query timeout in seconds. By default, the driver will wait
// indefinitely for results.
// SendStreamParamsAtExec
// Configures the driver to send all stream data at execution (true), or to
// send stream data in chunks (false). By default, the value is set to
// true. For more information, see sqlsrv_send_stream_data.
//
// Return Value
// A statement resource. If the statement resource cannot be created, false is returned.
PHP_FUNCTION( sqlsrv_prepare )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_prepare" );
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
ss_sqlsrv_conn* conn = NULL;
char *sql = NULL;
unsigned int sql_len = 0;
zval* params_z = NULL;
zval* options_z = NULL;
hash_auto_ptr ss_stmt_options_ht;
zval_auto_ptr stmt_z;
ALLOC_INIT_ZVAL( stmt_z );
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, &params_z, &options_z );
try {
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_stmt_options_ht );
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, NULL /*hashfn*/,
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
}
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( sql == NULL ) {
DIE( "sqlsrv_query: sql string was null." );
}
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
ss_stmt_options_ht, SS_STMT_OPTS,
ss_error_handler, NULL TSRMLS_CC ) );
core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC );
mark_params_by_reference( stmt, params_z TSRMLS_CC );
stmt->prepared = true;
// register the statement with the PHP runtime
ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC );
// store the resource id with the connection so the connection
// can release this statement when it closes.
int next_index = zend_hash_next_free_element( conn->stmts );
long rsrc_idx = Z_RESVAL_P( stmt_z );
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &rsrc_idx, sizeof( long ) TSRMLS_CC );
stmt->conn_index = next_index;
// the statement is now registered with EG( regular_list )
stmt.transferred();
zval_ptr_dtor( &return_value );
*return_value_ptr = stmt_z;
stmt_z.transferred();
}
catch( core::CoreException& ) {
if( stmt ) {
stmt->conn = NULL;
stmt->~ss_sqlsrv_stmt();
}
if( Z_TYPE_P( stmt_z ) != IS_NULL ) {
free_stmt_resource( stmt_z TSRMLS_CC );
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_prepare: Unknown exception caught." );
}
}
// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]])
//
// Creates a statement resource associated with the specified connection. The statement
// is immediately executed and may not be executed again using sqlsrv_execute.
//
// Parameters
// $conn: The connection resource associated with the created statement.
//
// $tsql: The Transact-SQL expression that corresponds to the created statement.
//
// $params [OPTIONAL]: An array of values that correspond to parameters in a
// parameterized query. Each parameter may be specified as:
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
// When given just a $value, the direction is default input, and phptype is the value
// given, with the sql type inferred from the php type.
//
// $options [OPTIONAL]: An associative array that sets query properties. The
// table below lists the supported keys and corresponding values:
// QueryTimeout
// Sets the query timeout in seconds. By default, the driver will wait
// indefinitely for results.
// SendStreamParamsAtExec
// Configures the driver to send all stream data at execution (true), or to
// send stream data in chunks (false). By default, the value is set to
// true. For more information, see sqlsrv_send_stream_data.
//
// Return Value
// A statement resource. If the statement resource cannot be created, false is returned.
PHP_FUNCTION( sqlsrv_query )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_query" );
ss_sqlsrv_conn* conn = NULL;
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
char* sql = NULL;
hash_auto_ptr ss_stmt_options_ht;
int sql_len = 0;
zval* options_z = NULL;
zval* params_z = NULL;
zval_auto_ptr stmt_z;
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, &params_z, &options_z );
try {
// check for statement options
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_stmt_options_ht );
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, NULL /*hashfn*/, ZVAL_PTR_DTOR,
0 /*persistent*/ TSRMLS_CC );
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
}
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( sql == NULL ) {
DIE( "sqlsrv_query: sql string was null." );
}
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
ss_stmt_options_ht, SS_STMT_OPTS,
ss_error_handler, NULL TSRMLS_CC ) );
stmt->params_z = params_z;
if( params_z ) {
zval_add_ref( &params_z );
}
stmt->set_func( "sqlsrv_query" );
bind_params( stmt TSRMLS_CC );
// execute the statement
core_sqlsrv_execute( stmt TSRMLS_CC, sql, sql_len );
// register the statement with the PHP runtime
ALLOC_INIT_ZVAL( stmt_z );
ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC );
// store the resource id with the connection so the connection
// can release this statement when it closes.
int next_index = zend_hash_next_free_element( conn->stmts );
long rsrc_idx = Z_RESVAL_P( stmt_z );
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &rsrc_idx, sizeof( long ) TSRMLS_CC );
stmt->conn_index = next_index;
stmt.transferred();
zval_ptr_dtor( &return_value );
*return_value_ptr = stmt_z;
stmt_z.transferred();
}
catch( core::CoreException& ) {
if( stmt ) {
stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself
stmt->~ss_sqlsrv_stmt();
}
if( stmt_z ) {
free_stmt_resource( stmt_z TSRMLS_CC );
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_query: Unknown exception caught." );
}
}
void free_stmt_resource( zval* stmt_z TSRMLS_DC )
{
int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_z ));
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RESVAL_P( stmt_z ));
}
ZVAL_NULL( stmt_z );
zval_ptr_dtor( &stmt_z );
}
// internal connection functions
namespace {
// must close all statement handles opened by this connection before closing the connection
// no errors are returned, since close should always succeed
void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC )
{
//pre-condition check
SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an "
"already destroyed connection.");
SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." );
// loop through the stmts hash table and destroy each stmt resource so we can close the
// ODBC connection
for( zend_hash_internal_pointer_reset( conn->stmts );
zend_hash_has_more_elements( conn->stmts ) == SUCCESS;
zend_hash_move_forward( conn->stmts )) {
long* rsrc_idx_ptr = NULL;
try {
// get the resource id for the next statement created with this connection
core::sqlsrv_zend_hash_get_current_data( *conn, conn->stmts, reinterpret_cast<void**>( &rsrc_idx_ptr ) TSRMLS_CC );
}
catch( core::CoreException& ) {
DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" );
}
// see if the statement is still valid, and if not skip to the next one
// presumably this should never happen because if it's in the list, it should still be valid
// by virtue that a statement resource should remove itself from its connection when it is
// destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource
// and move to the next one.
ss_sqlsrv_stmt* stmt = NULL;
int type = -1;
stmt = static_cast<ss_sqlsrv_stmt*>( zend_list_find( *rsrc_idx_ptr, &type ));
if( stmt == NULL || type != ss_sqlsrv_stmt::descriptor ) {
LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves"
" from the connection so this shouldn't be out of sync." );
continue;
}
// delete the statement by deleting it from Zend's resource list, which will force its destruction
stmt->conn = NULL;
try {
// this would call the destructor on the statement.
core::sqlsrv_zend_hash_index_del( *conn, &EG( regular_list ), *rsrc_idx_ptr TSRMLS_CC );
}
catch( core::CoreException& ) {
LOG( SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", *rsrc_idx_ptr );
}
}
zend_hash_destroy( conn->stmts );
FREE_HASHTABLE( conn->stmts );
conn->stmts = NULL;
}
int get_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, zval const* value_z TSRMLS_DC )
{
for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i )
{
if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( key, SS_CONN_OPTS[ i ].sqlsrv_name )) {
switch( SS_CONN_OPTS[ i ].value_type ) {
case CONN_ATTR_BOOL:
// bool attributes can be either strings to be appended to the connection string
// as yes or no or integral connection attributes. This will have to be reworked
// if we ever introduce a boolean connection option that maps to a string connection
// attribute.
break;
case CONN_ATTR_INT:
{
CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
SS_CONN_OPTS[ i ].sqlsrv_name )
{
throw ss::SSException();
}
break;
}
case CONN_ATTR_STRING:
{
CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
SS_CONN_OPTS[ i ].sqlsrv_name ) {
throw ss::SSException();
}
char* value = Z_STRVAL_P( value_z );
int value_len = Z_STRLEN_P( value_z );
bool escaped = core_is_conn_opt_value_escaped( value, value_len );
CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) {
throw ss::SSException();
}
break;
}
}
return SS_CONN_OPTS[ i ].conn_option_key;
}
}
return SQLSRV_CONN_OPTION_INVALID;
}
int get_stmt_option_key( char* key, unsigned int key_len TSRMLS_DC )
{
for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i )
{
if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( key, SS_STMT_OPTS[ i ].name )) {
return SS_STMT_OPTS[ i ].key;
}
}
return SQLSRV_STMT_OPTION_INVALID;
}
void add_stmt_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len,
HashTable* options_ht, zval** data TSRMLS_DC )
{
int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC );
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, key ) {
throw ss::SSException();
}
zval_add_ref( data ); // inc the ref count since this is going into the options_ht too.
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, (void**)data, sizeof(zval*) TSRMLS_CC );
}
void add_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len,
HashTable* options_ht, zval** data TSRMLS_DC )
{
int option_key = ::get_conn_option_key( ctx, key, key_len, *data TSRMLS_CC );
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key ) {
throw ss::SSException();
}
zval_add_ref( data ); // inc the ref count since this is going into the options_ht too.
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, (void**)data, sizeof(zval*) TSRMLS_CC );
}
// Iterates through the list of statement options provided by the user and validates them
// against the list of supported statement options by this driver. After validation
// creates a Hashtable of statement options to be sent to the core layer for processing.
void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* ss_stmt_options_ht TSRMLS_DC )
{
try {
if( stmt_options ) {
HashTable* options_ht = Z_ARRVAL_P( stmt_options );
for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS;
zend_hash_move_forward( options_ht )) {
int type = HASH_KEY_NON_EXISTANT;
char *key = NULL;
unsigned int key_len = 0;
unsigned long int_key = -1;
zval** data;
zval* conn_opt = NULL;
int result = 0;
type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &int_key, 0, NULL );
if( type != HASH_KEY_IS_STRING ) {
std::ostringstream itoa;
itoa << int_key;
CHECK_CUSTOM_ERROR( true , ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, itoa.str() ) {
throw core::CoreException();
}
}
core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, (void**) &data TSRMLS_CC );
add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC );
}
}
}
catch( core::CoreException& ) {
throw;
}
}
// Iterates through the list of connection options provided by the user and validates them
// against the predefined list of supported connection options by this driver. After validation
// creates a Hashtable of connection options to be sent to the core layer for processing.
void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, __out char** uid, __out char** pwd, __inout HashTable* ss_conn_options_ht TSRMLS_DC )
{
try {
if( user_options_z ) {
HashTable* options_ht = Z_ARRVAL_P( user_options_z );
for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS;
zend_hash_move_forward( options_ht )) {
int type = HASH_KEY_NON_EXISTANT;
char *key = NULL;
unsigned int key_len = 0;
unsigned long int_key = -1;
zval** data = NULL;
type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &int_key, 0, NULL );
CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) {
throw ss::SSException();
}
core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, (void**) &data TSRMLS_CC );
if( key_len == sizeof( SSConnOptionNames::UID ) && !stricmp( key, SSConnOptionNames::UID )) {
*uid = Z_STRVAL_PP( data );
}
else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( key, SSConnOptionNames::PWD )) {
*pwd = Z_STRVAL_PP( data );
}
else {
::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC );
}
}
}
}
catch( core::CoreException& ) {
throw;
}
}
} // namespace

View file

@ -1,761 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_conn.cpp
//
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <php.h>
#include <psapi.h>
#include <windows.h>
#include <winver.h>
#include <string>
#include <sstream>
// *** internal variables and constants ***
namespace {
// *** internal constants ***
// an arbitrary figure that should be large enough for most connection strings.
const int DEFAULT_CONN_STR_LEN = 2048;
// length of buffer used to retrieve information for client and server info buffers
const int INFO_BUFFER_LEN = 256;
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
// ODBC driver name.
const char CONNECTION_STRING_DRIVER_NAME[] = "Driver={ODBC Driver 11 for SQL Server};";
// default options if only the server is specified
const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}";
// connection option appended when no user name or password is given
const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};";
// connection option appended for MARS when MARS isn't explicitly mentioned
const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};";
// *** internal function prototypes ***
void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
HashTable* options_ht, const connection_option valid_conn_opts[],
void* driver,__inout std::string& connection_string TSRMLS_DC );
void determine_server_version( sqlsrv_conn* conn TSRMLS_DC );
const char* get_processor_arch( void );
void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC );
connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, unsigned int key_len TSRMLS_DC );
void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC );
}
// core_sqlsrv_connect
// opens a connection and returns a sqlsrv_conn structure.
// Parameters:
// henv_cp - connection pooled env context
// henv_ncp - non connection pooled env context
// server - name of the server we're connecting to
// uid - username
// pwd - password
// options_ht - zend_hash list of options
// err - error callback to put into the connection's context
// valid_conn_opts[] - array of valid driver supported connection options.
// driver - reference to caller
// Return
// A sqlsrv_conn structure. An exception is thrown if an error occurs
sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
const char* server, const char* uid, const char* pwd,
HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[],
void* driver, const char* driver_func TSRMLS_DC )
{
SQLRETURN r;
std::string conn_str;
conn_str.reserve( DEFAULT_CONN_STR_LEN );
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
sqlsrv_malloc_auto_ptr<wchar_t> wconn_string;
unsigned int wconn_len = 0;
try {
sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv
// check the connection pooling setting to determine which henv to use to allocate the connection handle
// we do this earlier because we have to allocate the connection handle prior to setting attributes on
// it in build_connection_string_and_set_conn_attr.
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
zval** option_zz = NULL;
int zr = SUCCESS;
zr = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING, reinterpret_cast<void**>( &option_zz ));
if( zr != FAILURE ) {
// if the option was found and it's not true, then use the non pooled environment handle
if(( Z_TYPE_PP( option_zz ) == IS_STRING && !core_str_zval_is_true( *option_zz )) || !zend_is_true( *option_zz ) ) {
henv = &henv_ncp;
}
}
}
SQLHANDLE temp_conn_h;
core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC );
conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC );
conn->set_func( driver_func );
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver,
conn_str TSRMLS_CC );
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
wconn_len = (conn_str.length() + 1) * sizeof( wchar_t );
wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), conn_str.length(), &wconn_len );
CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message() )
{
throw core::CoreException();
}
SQLSMALLINT output_conn_size;
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get() ),
static_cast<SQLSMALLINT>( wconn_len ), NULL,
0, &output_conn_size, SQL_DRIVER_NOPROMPT );
// clear the connection string from memory to remove sensitive data (such as a password).
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if( !SQL_SUCCEEDED( r )) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// if it's a IM002, meaning that the correct ODBC driver is not installed
CHECK_CUSTOM_ERROR( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' &&
state[4] == '2', conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) {
throw core::CoreException();
}
}
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
determine_server_version( conn TSRMLS_CC );
}
catch( std::bad_alloc& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn->invalidate();
DIE( "C++ memory allocation failure building the connection string." );
}
catch( std::out_of_range const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( std::length_error const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( core::CoreException& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
conn->invalidate();
throw;
}
sqlsrv_conn* return_conn = conn;
conn.transferred();
return return_conn;
}
// core_sqlsrv_begin_transaction
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
// the call to core_sqlsrv_begin_transaction and before any calls to
// core_sqlsrv_rollback or core_sqlsrv_commit.
// The default transaction mode is auto-commit. This means that all queries
// are automatically committed upon success unless they have been designated
// as part of an explicit transaction by using core_sqlsrv_begin_transaction.
// Parameters:
// sqlsrv_conn*: The connection with which the transaction is associated.
void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_commit
// Commits the current transaction on the specified connection and returns the
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
// core_sqlsrv_commit.
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_rollback
// Rolls back the current transaction on the specified connection and returns
// the connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
// core_sqlsrv_commit.
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_close
// Called when a connection resource is destroyed by the Zend engine.
// Parameters:
// conn - The current active connection.
void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC )
{
// if the connection wasn't successful, just return.
if( conn == NULL )
return;
try {
// rollback any transaction in progress (we don't care about the return result)
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
}
catch( core::CoreException& ) {
LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." );
}
// disconnect from the server
SQLRETURN r = SQLDisconnect( conn->handle() );
if( !SQL_SUCCEEDED( r )) {
LOG( SEV_ERROR, "Disconnect failed when closing the connection." );
}
// free the connection handle
conn->invalidate();
sqlsrv_free( conn );
}
// core_sqlsrv_prepare
// Create a statement object and prepare the SQL query passed in for execution at a later time.
// Parameters:
// stmt - statement to be prepared
// sql - T-SQL command to prepare
// sql_len - length of the T-SQL string
void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC )
{
try {
// convert the string from its encoding to UTf-16
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
sqlsrv_malloc_auto_ptr<wchar_t> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( sizeof( wchar_t )));
wsql_string[0] = L'\0';
wsql_len = 0;
}
else {
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
stmt->encoding() );
wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast<const char*>( sql ),
sql_len, &wsql_len );
CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
get_last_error_message() ) {
throw core::CoreException();
}
}
// prepare our wide char query string
core::SQLPrepareW( stmt, reinterpret_cast<SQLWCHAR*>( wsql_string.get() ), wsql_len TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_get_server_version
// Determines the vesrion of the SQL Server we are connected to. Calls a helper function
// get_server_version to get the version of SQL Server.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// *server_version - zval for returning results.
void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
ZVAL_STRINGL( server_version, buffer, buffer_len, 0 );
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_get_server_info
// Returns the Database name, the name of the SQL Server we are connected to
// and the version of the SQL Server.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// *server_info - zval for returning results.
void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
// initialize the array
core::sqlsrv_array_init( *conn, server_info TSRMLS_CC );
// Get the database name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the server version
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the server name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// core_sqlsrv_get_client_info
// Returns the ODBC driver's dll name, version and the ODBC version.
// Parameters
// conn - The connection resource by which the client and server are connected.
// *client_info - zval for returning the results.
void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
// initialize the array
core::sqlsrv_array_init( *conn, client_info TSRMLS_CC );
// Get the ODBC driver's dll name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the ODBC driver's ODBC version
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the OBDC driver's version
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// core_is_conn_opt_value_escaped
// determine if connection string value is properly escaped.
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
// the value will be surrounded by { and } by the caller after it has been validated
bool core_is_conn_opt_value_escaped( const char* value, int value_len )
{
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// unquoted since we quote it when adding it to the connection string.
if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
++value;
value_len -= 2;
}
// check to make sure that all right braces are escaped
int i = 0;
while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) {
// skip both braces
if( value[i] == '}' )
++i;
++i;
}
if( i < value_len && value[i] == '}' ) {
return false;
}
return true;
}
// *** internal connection functions and classes ***
namespace {
connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long key,
const connection_option conn_opts[] TSRMLS_DC )
{
for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) {
if( key == conn_opts[ opt_idx ].conn_option_key ) {
return &conn_opts[ opt_idx ];
}
}
SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." );
return NULL; // avoid a compiler warning
}
// says what it does, and does what it says
// rather than have attributes and connection strings as ODBC does, we unify them into a hash table
// passed to the connection, and then break them out ourselves and either set attributes or put the
// option in the connection string.
void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
HashTable* options, const connection_option valid_conn_opts[],
void* driver,__inout std::string& connection_string TSRMLS_DC )
{
bool credentials_mentioned = false;
bool mars_mentioned = false;
connection_option const* conn_opt;
int zr = SUCCESS;
try {
connection_string = CONNECTION_STRING_DRIVER_NAME;
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC );
// if uid is not present then we use trusted connection.
if(uid == NULL || strlen( uid ) == 0 ) {
connection_string += "Trusted_Connection={Yes};";
}
else {
bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC );
// if no password was given, then don't add a password to the connection string. Perhaps the UID
// given doesn't have a password?
if( pwd != NULL ) {
escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC );
}
}
// if no options were given, then we set MARS the defaults and return immediately.
if( options == NULL || zend_hash_num_elements( options ) == 0 ) {
connection_string += CONNECTION_STRING_DEFAULT_OPTIONS;
return;
}
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
// if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn"
// flag is set to false.
if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) {
zval** trace_value = NULL;
int zr = zend_hash_index_find( options, SQLSRV_CONN_OPTION_TRACE_ON, (void**)&trace_value );
if( zr == FAILURE || !zend_is_true( *trace_value )) {
zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE );
}
}
for( zend_hash_internal_pointer_reset( options );
zend_hash_has_more_elements( options ) == SUCCESS;
zend_hash_move_forward( options )) {
int type = HASH_KEY_NON_EXISTANT;
char *key = NULL;
unsigned int key_len = -1;
unsigned long index = -1;
zval** data = NULL;
type = zend_hash_get_current_key_ex( options, &key, &key_len, &index, 0, NULL );
// The driver layer should ensure a valid key.
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." );
core::sqlsrv_zend_hash_get_current_data( *conn, options, (void**) &data TSRMLS_CC );
conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC );
if( index == SQLSRV_CONN_OPTION_MARS ) {
mars_mentioned = true;
}
conn_opt->func( conn_opt, *data, conn, connection_string TSRMLS_CC );
}
// MARS on if not explicitly turned off
if( !mars_mentioned ) {
connection_string += CONNECTION_OPTION_MARS_ON;
}
}
catch( core::CoreException& ) {
throw;
}
}
// get_server_version
// Helper function which returns the version of the SQL Server we are connected to.
void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
*server_version = buffer;
len = buffer_len;
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
}
// get_processor_arch
// Calls GetSystemInfo to verify the what architecture of the processor is supported
// and return the string of the processor name.
const char* get_processor_arch( void )
{
SYSTEM_INFO sys_info;
GetSystemInfo( &sys_info);
switch( sys_info.wProcessorArchitecture ) {
case PROCESSOR_ARCHITECTURE_INTEL:
return PROCESSOR_ARCH[0];
case PROCESSOR_ARCHITECTURE_AMD64:
return PROCESSOR_ARCH[1];
case PROCESSOR_ARCHITECTURE_IA64:
return PROCESSOR_ARCH[2];
default:
DIE( "Unknown Windows processor architecture." );
return NULL;
}
}
// some features require a server of a certain version or later
// this function determines the version of the server we're connected to
// and stores it in the connection. Any errors are logged before return.
// Exception is thrown when the server version is either undetermined
// or is invalid (< 2000).
void determine_server_version( sqlsrv_conn* conn TSRMLS_DC )
{
SQLSMALLINT info_len;
char p[ INFO_BUFFER_LEN ];
core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC );
errno = 0;
char version_major_str[ 3 ];
SERVER_VERSION version_major;
memcpy( version_major_str, p, 2 );
version_major_str[ 2 ] = '\0';
version_major = static_cast<SERVER_VERSION>( atoi( version_major_str ));
CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION )
{
throw core::CoreException();
}
// SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least
// that high
conn->server_version = version_major;
}
void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC )
{
// wrap a connection option in a quote. It is presumed that any character that need to be escaped will
// be escaped, such as a closing }.
TSRMLS_C;
if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) {
++val;
val_len -= 2;
}
conn_str += odbc_name;
conn_str += "={";
conn_str.append( val, val_len );
conn_str += "};";
}
} // namespace
// simply add the parsed value to the connection string
void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str
TSRMLS_DC )
{
const char* val_str = Z_STRVAL_P( value );
int val_len = Z_STRLEN_P( value );
common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC );
}
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC )
{
TSRMLS_C;
}
// helper function to evaluate whether a string value is true or false.
// Values = ("true" or "1") are treated as true values. Everything else is treated as false.
// Returns 1 for true and 0 for false.
int core_str_zval_is_true( zval* value_z )
{
SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." );
char* value_in = Z_STRVAL_P( value_z );
int val_len = Z_STRLEN_P( value_z );
// strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
int last_char = val_len - 1;
while( isspace( value_in[ last_char ] )) {
value_in[ last_char ] = '\0';
val_len = last_char;
--last_char;
}
// save adjustments to the value made by stripping whitespace at the end
ZVAL_STRINGL( value_z, value_in, val_len, 0 );
const char VALID_TRUE_VALUE_1[] = "true";
const char VALID_TRUE_VALUE_2[] = "1";
if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) ||
( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len ))
) {
return 1; // true
}
return 0; // false
}

View file

@ -1,173 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_init.cpp
//
// Contents: common initialization routines shared by PDO and sqlsrv
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
// module global variables (initialized in minit and freed in mshutdown)
HMODULE g_sqlsrv_hmodule = NULL;
OSVERSIONINFO g_osversion;
// core_sqlsrv_minit
// Module initialization
// This function is called once per execution by the driver layer's MINIT function.
// The primary responsibility of this function is to allocate the two environment
// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC
// connection handle.
// Parameters:
// henv_cp - Environment handle for pooled connection.
// henv_ncp - Environment handle for non-pooled connection.
// err - Driver specific error handler which handles any errors during initialization.
void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC )
{
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long ));
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long ));
*henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL
try {
// get the version of the OS we're running on. For now this governs certain flags used by
// WideCharToMultiByte. It might be relevant to other things in the future.
g_osversion.dwOSVersionInfoSize = sizeof( g_osversion );
BOOL ver_return = GetVersionEx( &g_osversion );
if( !ver_return ) {
LOG( SEV_ERROR, "Failed to retrieve Windows version information." );
throw core::CoreException();
}
SQLHANDLE henv = SQL_NULL_HANDLE;
SQLRETURN r;
// allocate the non pooled environment handle
// we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so
// we use the direct ODBC function.
r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
if( !SQL_SUCCEEDED( r )) {
throw core::CoreException();
}
*henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL );
(*henv_ncp)->set_func( driver_func );
// set to ODBC 3
core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER
TSRMLS_CC );
// disable connection pooling
core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>( SQL_CP_OFF ),
SQL_IS_UINTEGER TSRMLS_CC );
// allocate the pooled envrionment handle
// we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so
// we use the direct ODBC function.
r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
if( !SQL_SUCCEEDED( r )) {
throw core::CoreException();
}
*henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL );
(*henv_cp)->set_func( driver_func );
// set to ODBC 3
core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC);
// enable connection pooling
core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>( SQL_CP_ONE_PER_HENV ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch( core::CoreException& e ) {
LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." );
if( *henv_ncp != NULL ) {
// free the ODBC env handle allocated just above
SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp );
delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap)
*henv_ncp = NULL;
}
if( *henv_cp != NULL ) {
// free the ODBC env handle allocated just above
SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp );
delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap)
*henv_cp = NULL;
}
throw e; // rethrow for the driver to catch
}
catch( std::bad_alloc& e ) {
LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." );
if( *henv_ncp != NULL ) {
SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp );
delete *henv_ncp;
*henv_ncp = NULL;
}
if( *henv_cp ) {
SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp );
delete *henv_cp;
*henv_cp = NULL;
}
throw e; // rethrow for the driver to catch
}
}
// core_sqlsrv_mshutdown
// Module shutdown function
// Free the environment handles allocated in MINIT and unregister our stream wrapper.
// Resource types and constants are automatically released since we don't flag them as
// persistent when they are registered.
// Parameters:
// henv_cp - Pooled environment handle.
// henv_ncp - Non-pooled environment handle.
void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp )
{
if( henv_ncp != SQL_NULL_HANDLE ) {
henv_ncp.invalidate();
}
if( henv_cp != SQL_NULL_HANDLE ) {
henv_cp.invalidate();
}
return;
}
// DllMain for the extension.
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID )
{
switch( fdwReason ) {
case DLL_PROCESS_ATTACH:
// store the module handle for use by client_info and server_info
g_sqlsrv_hmodule = hinstDLL;
break;
default:
break;
}
return TRUE;
}

View file

@ -1,1339 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_results.cpp
//
// Contents: Result sets
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <functional>
#include <iostream>
#include <sstream>
using namespace core;
// conversion matrix
// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported
// this is initialized the first time the buffered result set is created.
sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix;
namespace {
// *** internal types ***
#pragma warning(disable:4200)
// *** internal constants ***
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
// *** internal functions ***
// return an integral type rounded up to a certain number
template <int align, typename T>
T align_to( T number )
{
DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" );
return ((number % align) == 0) ? number : (number + align - (number % align));
}
// return a pointer address aligned to a certain address boundary
template <int align, typename T>
T* align_to( T* ptr )
{
size_t p_value = (size_t) ptr;
return align_to<align, size_t>( p_value );
}
// set the nth bit of the bitstream starting at ptr
void set_bit( void* ptr, unsigned int bit )
{
unsigned char* null_bits = reinterpret_cast<unsigned char*>( ptr );
null_bits += bit >> 3;
*null_bits |= 1 << ( 7 - ( bit & 0x7 ));
}
// retrieve the nth bit from the bitstream starting at ptr
bool get_bit( void* ptr, unsigned int bit )
{
unsigned char* null_bits = reinterpret_cast<unsigned char*>( ptr );
null_bits += bit >> 3;
return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0);
}
// read in LOB field during buffered result creation
SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
unsigned long mem_used TSRMLS_DC );
// dtor for each row in the cache
void cache_row_dtor( void* data );
// convert a number to a string using locales
// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of
// subclassing a new streambuf just to avoid the copy, it's easier to do the copy
template <typename Char, typename Number>
SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
sqlsrv_error_auto_ptr& last_error )
{
std::basic_ostringstream<Char> os;
std::locale loc;
os.imbue( loc );
std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream<Char>::_Iter( os.rdbuf() ), os, ' ', *number_data );
std::basic_string<Char>& str_num = os.str();
if( os.fail() ) {
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
(SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 );
return SQL_ERROR;
}
if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) {
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
(SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 );
return SQL_ERROR;
}
*out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator
memcpy( buffer, str_num.c_str(), *out_buffer_length );
return SQL_SUCCESS;
}
template <typename Number, typename Char>
SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error )
{
Number* number_data = reinterpret_cast<Number*>( buffer );
std::locale loc; // default locale should match system
std::basic_istringstream<Char> is;
is.str( string_data );
is.imbue( loc );
std::ios_base::iostate st = 0;
std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream<Char>::_Iter( is.rdbuf( ) ),
std::basic_istream<Char>::_Iter(0), is, st, *number_data );
if( st & std::ios_base::failbit ) {
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
(SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 );
return SQL_ERROR;
}
*out_buffer_length = sizeof( Number );
return SQL_SUCCESS;
}
// "closure" for the hash table destructor
struct row_dtor_closure {
sqlsrv_buffered_result_set* results;
BYTE* row_data;
row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) :
results( st ), row_data( row )
{
}
};
sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number )
{
SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ];
SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLINTEGER native_code;
SQLSMALLINT wnative_message_len = 0;
SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message,
SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len );
if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
return NULL;
}
// convert the error into the encoding of the context
SQLSRV_ENCODING enc = odbc->encoding();
if( enc == SQLSRV_ENCODING_DEFAULT ) {
enc = odbc->conn->encoding();
}
// convert the error into the encoding of the context
sqlsrv_malloc_auto_ptr<SQLCHAR> sql_state;
SQLINTEGER sql_state_len = 0;
if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) {
return NULL;
}
sqlsrv_malloc_auto_ptr<SQLCHAR> native_message;
SQLINTEGER native_message_len = 0;
if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) {
return NULL;
}
return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message,
native_code );
}
} // namespace
// base class result set
sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) :
odbc( stmt )
{
}
// ODBC result set
// This object simply wraps ODBC function calls
sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) :
sqlsrv_result_set( stmt )
{
}
sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void )
{
}
SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC );
}
SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC );
}
SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length,
out_buffer_length TSRMLS_CC );
}
sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return odbc_get_diag_rec( odbc, record_number );
}
SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLRowCount( odbc TSRMLS_CC );
}
// Buffered result set
// This class holds a result set in memory
sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) :
sqlsrv_result_set( stmt ),
cache(NULL),
col_count(0),
meta(NULL),
current(0),
last_field_index(-1),
read_so_far(0)
{
// 10 is an arbitrary number for now for the initial size of the cache
ALLOC_HASHTABLE( cache );
core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, NULL /* hashfn */, cache_row_dtor /*dtor*/, 0 /*persistent*/
TSRMLS_CC );
col_count = core::SQLNumResultCols( stmt TSRMLS_CC );
// there is no result set to buffer
if( col_count == 0 ) {
return;
}
SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags
meta = static_cast<sqlsrv_buffered_result_set::meta_data*>( sqlsrv_malloc( col_count *
sizeof( sqlsrv_buffered_result_set::meta_data )));
// set up the conversion matrix if this is the first time we're called
if( conv_matrix.size() == 0 ) {
conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string;
conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double;
conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double;
conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long;
conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string;
conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string;
conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double;
conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string;
conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long;
conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string;
}
// get the meta data and calculate the size of a row buffer
SQLULEN offset = null_bytes;
for( SQLSMALLINT i = 0; i < col_count; ++i ) {
core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC );
offset = align_to<4>( offset );
meta[i].offset = offset;
switch( meta[i].type ) {
// these types are the display size
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL,
reinterpret_cast<SQLLEN*>( &meta[i].length ) TSRMLS_CC );
meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space
offset += meta[i].length;
break;
// these types are the column size
case SQL_BINARY:
case SQL_CHAR:
case SQL_SS_UDT:
case SQL_VARBINARY:
case SQL_VARCHAR:
// var* field types are length prefixed
if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
offset += sizeof( void* );
}
else {
meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space
offset += meta[i].length;
}
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
offset += sizeof( void* );
}
else {
meta[i].length *= sizeof( WCHAR );
meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space
offset += meta[i].length;
}
break;
// these types are LOBs
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN;
offset += sizeof( void* );
break;
// these types are the ISO date size
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_TYPE_TIMESTAMP:
core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL,
reinterpret_cast<SQLLEN*>( &meta[i].length ) TSRMLS_CC );
meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space
offset += meta[i].length;
break;
// these types are the native size
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
meta[i].length = sizeof( long );
offset += meta[i].length;
break;
case SQL_REAL:
case SQL_FLOAT:
meta[i].length = sizeof( double );
offset += meta[i].length;
break;
default:
SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" );
break;
}
switch( meta[i].type ) {
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DATETIME:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_LONGVARCHAR:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_XML:
case SQL_TYPE_TIMESTAMP:
case SQL_VARCHAR:
meta[i].c_type = SQL_C_CHAR;
break;
case SQL_SS_UDT:
case SQL_LONGVARBINARY:
case SQL_BINARY:
case SQL_VARBINARY:
meta[i].c_type = SQL_C_BINARY;
break;
case SQL_WLONGVARCHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
meta[i].c_type = SQL_C_WCHAR;
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
meta[i].c_type = SQL_C_LONG;
break;
case SQL_REAL:
case SQL_FLOAT:
meta[i].c_type = SQL_C_DOUBLE;
break;
default:
SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" );
break;
}
}
// read the data into the cache
// (offset from the above loop has the size of the row buffer necessary)
unsigned long mem_used = 0;
unsigned long row_count = 0;
while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) {
// allocate the row buffer
unsigned char* row = static_cast<unsigned char*>( sqlsrv_malloc( offset ));
memset( row, 0, offset );
// read the fields into the row buffer
for( SQLSMALLINT i = 0; i < col_count; ++i ) {
SQLLEN out_buffer_temp = SQL_NULL_DATA;
SQLPOINTER buffer;
SQLLEN* out_buffer_length = &out_buffer_temp;
switch( meta[i].c_type ) {
case SQL_C_CHAR:
case SQL_C_WCHAR:
case SQL_C_BINARY:
if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
out_buffer_length = &out_buffer_temp;
SQLPOINTER* lob_addr = reinterpret_cast<SQLPOINTER*>( &row[ meta[i].offset ] );
*lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC );
// a NULL pointer means NULL field
if( *lob_addr == NULL ) {
*out_buffer_length = SQL_NULL_DATA;
}
else {
*out_buffer_length = **reinterpret_cast<SQLLEN**>( lob_addr );
mem_used += *out_buffer_length;
}
}
else {
mem_used += meta[i].length;
CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
buffer = row + meta[i].offset + sizeof( SQLULEN );
out_buffer_length = reinterpret_cast<SQLLEN*>( row + meta[i].offset );
core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
false TSRMLS_CC );
}
break;
case SQL_C_LONG:
case SQL_C_DOUBLE:
{
mem_used += meta[i].length;
CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
buffer = row + meta[i].offset;
out_buffer_length = &out_buffer_temp;
core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
false TSRMLS_CC );
}
break;
default:
SQLSRV_ASSERT( false, "Unknown C type" );
break;
}
if( *out_buffer_length == SQL_NULL_DATA ) {
unsigned char* null_bits = reinterpret_cast<unsigned char*>( row );
set_bit( row, i );
}
}
SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" );
// add it to the cache
row_dtor_closure cl( this, row );
sqlsrv_zend_hash_next_index_insert( *stmt, cache, &cl, sizeof( cl ) TSRMLS_CC );
}
}
sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void )
{
// free the rows
if( cache ) {
zend_hash_destroy( cache );
FREE_HASHTABLE( cache );
cache = NULL;
}
// free the meta data
if( meta ) {
efree( meta );
meta = NULL;
}
}
SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC )
{
last_error = NULL;
last_field_index = -1;
read_so_far = 0;
switch( orientation ) {
case SQL_FETCH_NEXT:
offset = 1;
orientation = SQL_FETCH_RELATIVE;
break;
case SQL_FETCH_PRIOR:
offset = -1;
orientation = SQL_FETCH_RELATIVE;
break;
}
switch( orientation ) {
case SQL_FETCH_FIRST:
current = 1;
break;
case SQL_FETCH_LAST:
current = row_count( TSRMLS_C );
break;
case SQL_FETCH_ABSOLUTE:
current = offset;
break;
case SQL_FETCH_RELATIVE:
current += offset;
break;
default:
SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." );
break;
}
// check validity of current row
// the cursor can never get further away than just before the first row
if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) {
current = 0;
return SQL_NO_DATA;
}
// the cursor can never get further away than just after the last row
if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) {
current = row_count( TSRMLS_C ) + 1;
return SQL_NO_DATA;
}
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )
{
last_error = NULL;
field_index--; // convert from 1 based to 0 based
SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" );
if( field_index != last_field_index ) {
last_field_index = field_index;
read_so_far = 0;
}
unsigned char* row = get_row();
// if the field is null, then return SQL_NULL_DATA
if( get_bit( row, field_index )) {
*out_buffer_length = SQL_NULL_DATA;
return SQL_SUCCESS;
}
// check to make sure the conversion type is valid
if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() ||
conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) ==
conv_matrix.find( meta[ field_index ].c_type )->second.end() ) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "07006",
(SQLCHAR*) "Restricted data type attribute violation", 0 );
return SQL_ERROR;
}
return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length,
out_buffer_length );
}
SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC )
{
SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" );
SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE,
"Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" );
SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE,
"Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" );
if( last_error == NULL ) {
return SQL_NO_DATA;
}
SQLSRV_ASSERT( last_error->sqlstate != NULL,
"Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" );
memcpy( diag_info_buffer, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE ));
return SQL_SUCCESS;
}
unsigned char* sqlsrv_buffered_result_set::get_row( void )
{
row_dtor_closure* cl_ptr;
int zr = zend_hash_index_find( cache, current - 1, (void**) &cl_ptr );
SQLSRV_ASSERT( zr == SUCCESS, "Failed to find row %1!d! in the cache", current );
return cl_ptr->row_data;
}
sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number )
{
// we only hold a single error if there is one, otherwise return the ODBC error(s)
if( last_error == NULL ) {
return odbc_get_diag_rec( odbc, record_number );
}
if( record_number > 1 ) {
return NULL;
}
return new (sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code );
}
SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D )
{
last_error = NULL;
return zend_hash_num_elements( cache );
}
// private functions
template <typename Char>
SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out void* buffer,
SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
sqlsrv_error_auto_ptr& out_error )
{
// hex characters for the conversion loop below
static char hex_chars[] = "0123456789ABCDEF";
SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" );
SQLRETURN r = SQL_ERROR;
// Set the amount of space necessary for null characters at the end of the data.
SQLSMALLINT extra = sizeof(Char);
SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or "
"multiple of 4 for binary to wide string" );
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
// *2 is for each byte to hex conversion and * extra is for either system or wide string allocation
*out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra;
// copy as much as we can into the buffer
SQLLEN to_copy;
if( buffer_length < *out_buffer_length + extra ) {
to_copy = (buffer_length - extra);
out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
r = SQL_SUCCESS;
to_copy = *out_buffer_length;
}
// if there are bytes to copy as hex
if( to_copy > 0 ) {
// quick hex conversion routine
Char* h = reinterpret_cast<Char*>( buffer );
BYTE* b = reinterpret_cast<BYTE*>( field_data );
// to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
// to get the number of hex digits we can copy
SQLLEN to_copy_hex = to_copy / (2 * extra);
for( int i = 0; i < to_copy_hex; ++i ) {
*h = hex_chars[ (*b & 0xf0) >> 4 ];
h++;
*h = hex_chars[ (*b++ & 0x0f) ];
h++;
}
read_so_far += to_copy_hex;
*h = static_cast<Char>( 0 );
}
else {
reinterpret_cast<char*>( buffer )[0] = '\0';
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLCHAR* row = get_row();
SQLCHAR* field_data = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
}
else {
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
}
return binary_to_string<char>( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLCHAR* row = get_row();
SQLCHAR* field_data = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
}
else {
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
}
return binary_to_string<WCHAR>( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" );
SQLSRV_ASSERT( buffer_length >= sizeof(LONG), "Buffer length must be able to find a long in "
"sqlsrv_buffered_result_set::double_to_long" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
LONG* long_data = reinterpret_cast<LONG*>( buffer );
if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003",
(SQLCHAR*) "Numeric value out of range", 0 );
return SQL_ERROR;
}
if( *double_data != floor( *double_data )) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07",
(SQLCHAR*) "Fractional truncation", 0 );
return SQL_SUCCESS_WITH_INFO;
}
*long_data = static_cast<LONG>( *double_data );
*out_buffer_length = sizeof( LONG );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
return number_to_string<char>( double_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
return number_to_string<WCHAR>( double_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" );
SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" );
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( buffer );
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
*double_data = static_cast<LONG>( *long_data );
*out_buffer_length = sizeof( double );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" );
unsigned char* row = get_row();
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
return number_to_string<char>( long_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" );
SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" );
unsigned char* row = get_row();
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
return number_to_string<WCHAR>( long_data, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" );
SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" );
unsigned char* row = get_row();
char* string_data = reinterpret_cast<char*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
return string_to_number<double>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" );
SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" );
unsigned char* row = get_row();
SQLWCHAR* string_data = reinterpret_cast<SQLWCHAR*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
return string_to_number<double>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" );
SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" );
unsigned char* row = get_row();
char* string_data = reinterpret_cast<char*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
return string_to_number<LONG>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" );
SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" );
unsigned char* row = get_row();
SQLWCHAR* string_data = reinterpret_cast<SQLWCHAR*>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
return string_to_number<LONG>( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" );
SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" );
SQLRETURN r = SQL_ERROR;
unsigned char* row = get_row();
SQLCHAR* field_data = NULL;
SQLULEN field_len = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_len = **reinterpret_cast<SQLLEN**>( &row[ meta[ field_index ].offset ] );
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far;
}
else {
field_len = *reinterpret_cast<SQLLEN*>( &row[ meta[ field_index ].offset ] );
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far;
}
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
*out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR);
// to_copy is the number of characters to copy, not including the null terminator
// supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair.
SQLLEN to_copy;
if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) {
to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
r = SQL_SUCCESS;
to_copy = field_len - read_so_far;
}
if( to_copy > 0 ) {
bool tried_again = false;
do {
int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, to_copy,
(LPWSTR) buffer, to_copy );
if( ch_space == 0 ) {
switch( GetLastError() ) {
case ERROR_NO_UNICODE_TRANSLATION:
// the theory here is the conversion failed because the end of the buffer we provided contained only
// half a character at the end
if( !tried_again ) {
to_copy--;
tried_again = true;
continue;
}
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
break;
default:
SQLSRV_ASSERT( false, "Severe error translating Unicode" );
break;
}
return SQL_ERROR;
}
((WCHAR*)buffer)[ to_copy ] = L'\0';
read_so_far += to_copy;
break;
} while( true );
}
else {
reinterpret_cast<WCHAR*>( buffer )[0] = L'\0';
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" );
SQLRETURN r = SQL_ERROR;
unsigned char* row = get_row();
// Set the amount of space necessary for null characters at the end of the data.
SQLSMALLINT extra = 0;
switch( meta[ field_index ].c_type ) {
case SQL_C_WCHAR:
extra = sizeof( SQLWCHAR );
break;
case SQL_C_BINARY:
extra = 0;
break;
case SQL_C_CHAR:
extra = sizeof( SQLCHAR );
break;
default:
SQLSRV_ASSERT( false, "Invalid type in get_string_data" );
break;
}
SQLCHAR* field_data = NULL;
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
}
else {
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
}
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
*out_buffer_length = *reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far;
// copy as much as we can into the buffer
SQLLEN to_copy;
if( buffer_length < *out_buffer_length + extra ) {
to_copy = buffer_length - extra;
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
r = SQL_SUCCESS;
to_copy = *out_buffer_length;
}
SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" );
if( to_copy > 0 ) {
memcpy( buffer, field_data + read_so_far, to_copy );
read_so_far += to_copy;
}
if( extra ) {
OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
memcpy( reinterpret_cast<SQLCHAR*>( buffer ) + to_copy, L"\0", extra );
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" );
SQLRETURN r = SQL_ERROR;
unsigned char* row = get_row();
SQLCHAR* field_data = NULL;
SQLULEN field_len = NULL;
// if this is the first time called for this field, just convert the entire string to system first then
// use that to read from instead of converting chunk by chunk. This is because it's impossible to know
// the total length of the string for output_buffer_length without doing the conversion and returning
// SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and
// to_same_string).
if( read_so_far == 0 ) {
if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
field_len = **reinterpret_cast<SQLLEN**>( &row[ meta[ field_index ].offset ] );
field_data = *reinterpret_cast<SQLCHAR**>( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far;
}
else {
field_len = *reinterpret_cast<SQLLEN*>( &row[ meta[ field_index ].offset ] );
field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far;
}
BOOL default_char_used = FALSE;
char default_char = '?';
// allocate enough to handle WC -> DBCS conversion if it happens
temp_string = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( field_len, sizeof( char ), sizeof(char)));
temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, field_len / sizeof(WCHAR),
(LPSTR) temp_string.get(), field_len, &default_char, &default_char_used );
if( temp_length == 0 ) {
switch( GetLastError() ) {
case ERROR_NO_UNICODE_TRANSLATION:
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
break;
default:
SQLSRV_ASSERT( false, "Severe error translating Unicode" );
break;
}
return SQL_ERROR;
}
}
*out_buffer_length = (temp_length - read_so_far);
SQLLEN to_copy = 0;
if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) {
to_copy = buffer_length - sizeof(char);
last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
r = SQL_SUCCESS_WITH_INFO;
}
else {
to_copy = (temp_length - read_so_far);
r = SQL_SUCCESS;
}
if( to_copy > 0 ) {
memcpy( buffer, temp_string.get() + read_so_far, to_copy );
}
SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" );
OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" );
((SQLCHAR*) buffer)[ to_copy ] = '\0';
read_so_far += to_copy;
return r;
}
SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
return to_same_string( field_index, buffer, buffer_length, out_buffer_length );
}
SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" );
SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this
unsigned char* row = get_row();
LONG* long_data = reinterpret_cast<LONG*>( &row[ meta[ field_index ].offset ] );
memcpy( buffer, long_data, sizeof( LONG ));
*out_buffer_length = sizeof( LONG );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length )
{
SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" );
SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this
unsigned char* row = get_row();
double* double_data = reinterpret_cast<double*>( &row[ meta[ field_index ].offset ] );
memcpy( buffer, double_data, sizeof( double ));
*out_buffer_length = sizeof( double );
return SQL_SUCCESS;
}
namespace {
// called for each row in the cache when the cache is destroyed in the destructor
void cache_row_dtor( void* data )
{
row_dtor_closure* cl = reinterpret_cast<row_dtor_closure*>( data );
BYTE* row = cl->row_data;
// don't release this here, since this is called from the destructor of the result_set
sqlsrv_buffered_result_set* result_set = cl->results;
for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) {
if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
void* out_of_row_data = *reinterpret_cast<void**>( &row[ result_set->col_meta_data(i).offset ] );
sqlsrv_free( out_of_row_data );
}
}
sqlsrv_free( row );
}
SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
unsigned long mem_used TSRMLS_DC )
{
SQLSMALLINT extra = 0;
SQLLEN* output_buffer_len = NULL;
// Set the amount of space necessary for null characters at the end of the data.
switch( meta.c_type ) {
case SQL_C_WCHAR:
extra = sizeof( SQLWCHAR );
break;
case SQL_C_BINARY:
extra = 0;
break;
case SQL_C_CHAR:
extra = sizeof( SQLCHAR );
break;
default:
SQLSRV_ASSERT( false, "Invalid type in read_lob_field" );
break;
}
SQLLEN already_read = 0;
SQLLEN to_read = INITIAL_FIELD_STRING_LEN;
sqlsrv_malloc_auto_ptr<char> buffer;
buffer = static_cast<char*>( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN )));
SQLRETURN r = SQL_SUCCESS;
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLLEN last_field_len = 0;
bool full_length_returned = false;
do {
output_buffer_len = reinterpret_cast<SQLLEN*>( buffer.get() );
r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ),
to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC );
// if the field is NULL, then return a NULL pointer
if( last_field_len == SQL_NULL_DATA ) {
return NULL;
}
// if the last read was successful, we're done
if( r == SQL_SUCCESS ) {
// check to make sure we haven't overflown our memory limit
CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
break;
}
// else if it wasn't the truncated warning (01004) then we're done
else if( r == SQL_SUCCESS_WITH_INFO ) {
SQLSMALLINT len;
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
TSRMLS_CC );
if( !is_truncated_warning( state )) {
break;
}
}
SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" );
// if the type of the field returns the total to be read, we use that and preallocate the buffer
if( last_field_len != SQL_NO_TOTAL ) {
CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
already_read += to_read - already_read;
to_read = last_field_len;
buffer.resize( to_read + extra + sizeof( SQLULEN ));
output_buffer_len = reinterpret_cast<SQLLEN*>( buffer.get() );
// record the size of the field since we have it available
*output_buffer_len = last_field_len;
full_length_returned = true;
}
// otherwise allocate another chunk of memory to read in
else {
already_read += to_read - already_read;
to_read *= 2;
CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
throw core::CoreException();
}
buffer.resize( to_read + extra + sizeof( SQLULEN ));
output_buffer_len = reinterpret_cast<SQLLEN*>( buffer.get() );
}
} while( true );
SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" );
// most LOB field types return the total length in the last_field_len, but some field types such as XML
// only return the amount read on the last read
if( !full_length_returned ) {
*output_buffer_len = already_read + last_field_len;
}
char* return_buffer = buffer;
buffer.transferred();
return return_buffer;
}
}

View file

@ -1,2189 +0,0 @@
#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 3.2 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 HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef PHP_WIN32
#define PHP_SQLSRV_API __declspec(dllexport)
#else
#define PHP_SQLSRV_API
#endif
// OACR is an internal Microsoft static code analysis tool
#if defined(OACR)
#include <oacr.h>
OACR_WARNING_PUSH
OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." )
OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." )
OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." )
OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." )
OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." )
OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." )
#else
// define to eliminate static analysis hints in the code
#define OACR_WARNING_SUPPRESS( warning, msg )
#endif
extern "C" {
#pragma warning(push)
#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 )
#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
#include "php.h"
#include "php_globals.h"
#include "php_ini.h"
#include "ext/standard/php_standard.h"
#include "ext/standard/info.h"
#pragma warning(pop)
#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
#include <sql.h>
#include <sqlext.h>
#if !defined(WC_ERR_INVALID_CHARS)
// imported from winnls.h as it isn't included by 5.3.0
#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars
#endif
// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when
// we use std::copy, which causes compilation to fail since we compile with warnings as errors.
#if defined(ZEND_DEBUG) && defined(inline)
#undef inline
#endif
#include <deque>
#include <map>
#include <algorithm>
#include <limits>
#include <cassert>
#include <strsafe.h>
// included for SQL Server specific constants
#include "msodbcsql.h"
//*********************************************************************************************************************************
// Constants and Types
//*********************************************************************************************************************************
// constants for maximums in SQL Server
const int SS_MAXCOLNAMELEN = 128;
const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
const int SQL_SERVER_MAX_PRECISION = 38;
const int SQL_SERVER_MAX_TYPE_SIZE = 0;
const int SQL_SERVER_MAX_PARAMS = 2100;
// max size of a date time string when converting from a DateTime object to a string
const int MAX_DATETIME_STRING_LEN = 256;
// precision and scale for the date time types between servers
const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23;
const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3;
const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34;
const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7;
// types for conversions on output parameters (though they can be used for input parameters, they are ignored)
enum SQLSRV_PHPTYPE {
MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type
SQLSRV_PHPTYPE_NULL = 1,
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STREAM,
MAX_SQLSRV_PHPTYPE, // highest value for a php type
SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid
};
// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting
// information as a string or a stream.
enum SQLSRV_ENCODING {
SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables.
SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection
SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData
SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData
SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR,
SQLSRV_ENCODING_UTF8 = CP_UTF8,
};
// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object.
enum SQLSRV_FETCH_TYPE {
MIN_SQLSRV_FETCH = 1, // lowest value for fetch type
SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices
SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names
SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys
MAX_SQLSRV_FETCH = 3, // highest value for fetch type
};
// buffer size of a sql state (including the null character)
const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1;
// buffer size allocated to retrieve data from a PHP stream. This number
// was chosen since PHP doesn't return more than 8k at a time even if
// the amount requested was more.
const int PHP_STREAM_BUFFER_SIZE = 8192;
// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants.
// The size is the column size or precision, and scale is the decimal digits for precise numeric types.
union sqlsrv_sqltype {
struct typeinfo_t {
int type:9;
int size:14;
int scale:8;
} typeinfo;
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;
long value;
};
// static assert for enforcing compile time conditions
template <bool b>
struct sqlsrv_static_assert;
template <>
struct sqlsrv_static_assert<true> { static const int value = 1; };
#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() )
//*********************************************************************************************************************************
// Logging
//*********************************************************************************************************************************
// log_callback
// a driver specific callback for logging messages
// severity - severity of the message: notice, warning, or error
// msg - the message to log in a FormatMessage style formatting
// print_args - args to the message
typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args );
// each driver must register a log callback. This should be the first thing a driver does.
void core_sqlsrv_register_logger( log_callback );
// a simple wrapper around a PHP error logging function.
void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... );
// a macro to make it convenient to use the function.
#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ )
// mask for filtering which severities are written to the log
enum logging_severity {
SEV_ERROR = 0x01,
SEV_WARNING = 0x02,
SEV_NOTICE = 0x04,
SEV_ALL = -1,
};
// Kill the PHP process and log the message to PHP
void die( const char* msg, ... );
#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); }
//*********************************************************************************************************************************
// Resource/Memory Management
//*********************************************************************************************************************************
// the macro max is defined and overrides the call to max in the allocator class
#pragma push_macro( "max" )
#undef max
// new memory allocation/free debugging facilities to help us verify that all allocations are being
// released in a timely manner and not just at the end of the script.
// Zend has memory logging and checking, but it can generate a lot of noise for just one extension.
// It's meant for internal use but might be useful for people adding features to our extension.
// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases
// must be done with sqlsrv_malloc and sqlsrv_free.
// #define SQLSRV_MEM_DEBUG 1
#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG )
inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line )
{
void* ptr = emalloc( size );
LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr );
return ptr;
}
inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line )
{
OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" );
if(( element_count > 0 && element_size > 0 ) &&
( element_count > element_size * element_count || element_size > element_size * element_count )) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count > element_size * element_count + extra ) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count + extra == 0 ) {
DIE( "Allocation size must be more than 0" );
}
void* ptr = emalloc( element_size * element_count + extra );
LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr );
return ptr;
}
inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line )
{
void* ptr = erealloc( original, size );
LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original );
return ptr;
}
inline void sqlsrv_free_trace( void* ptr, const char* file, int line )
{
LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line );
efree( ptr );
}
#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ )
#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ )
#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ )
#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ )
#else
inline void* sqlsrv_malloc( size_t size )
{
return emalloc( size );
}
inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra )
{
OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" );
if(( element_count > 0 && element_size > 0 ) &&
( element_count > element_size * element_count || element_size > element_size * element_count )) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count > element_size * element_count + extra ) {
DIE( "Integer overflow in sqlsrv_malloc" );
}
if( element_size * element_count + extra == 0 ) {
DIE( "Allocation size must be more than 0" );
}
return emalloc( element_size * element_count + extra );
}
inline void* sqlsrv_realloc( void* buffer, size_t size )
{
return erealloc( buffer, size );
}
inline void sqlsrv_free( void* ptr )
{
efree( ptr );
}
#endif
// trait class that allows us to assign const types to an auto_ptr
template <typename T>
struct remove_const {
typedef T type;
};
template <typename T>
struct remove_const<const T*> {
typedef T* type;
};
// allocator that uses the zend memory manager to manage memory
// this allows us to use STL classes that still work with Zend objects
template<typename T>
struct sqlsrv_allocator {
// typedefs used by the STL classes
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// conversion typedef (used by list and other STL classes)
template<typename U>
struct rebind {
typedef sqlsrv_allocator<U> other;
};
inline sqlsrv_allocator() {}
inline ~sqlsrv_allocator() {}
inline sqlsrv_allocator( sqlsrv_allocator const& ) {}
template<typename U>
inline sqlsrv_allocator( sqlsrv_allocator<U> const& ) {}
// address (doesn't work if the class defines operator&)
inline pointer address( reference r )
{
return &r;
}
inline const_pointer address( const_reference r )
{
return &r;
}
// memory allocation/deallocation
inline pointer allocate( size_type cnt,
typename std::allocator<void>::const_pointer = 0 )
{
return reinterpret_cast<pointer>( sqlsrv_malloc(cnt, sizeof (T), 0));
}
inline void deallocate( pointer p, size_type )
{
sqlsrv_free(p);
}
// size
inline size_type max_size( void ) const
{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
// object construction/destruction
inline void construct( pointer p, const T& t )
{
new(p) T(t);
}
inline void destroy(pointer p)
{
p->~T();
}
// equality operators
inline bool operator==( sqlsrv_allocator const& )
{
return true;
}
inline bool operator!=( sqlsrv_allocator const& a )
{
return !operator==(a);
}
};
// base class for auto_ptrs that we define below. It provides common operators and functions
// used by all the classes.
template <typename T, typename Subclass>
class sqlsrv_auto_ptr {
public:
sqlsrv_auto_ptr( void ) : _ptr( NULL )
{
}
~sqlsrv_auto_ptr( void )
{
static_cast<Subclass*>(this)->reset( NULL );
}
// call when ownership is transferred
void transferred( void )
{
_ptr = NULL;
}
// explicit function to get the pointer.
T* get( void ) const
{
return _ptr;
}
// cast operator to allow auto_ptr to be used where a normal const * can be.
operator const T* () const
{
return _ptr;
}
// cast operator to allow auto_ptr to be used where a normal pointer can be.
operator typename remove_const<T*>::type () const
{
return _ptr;
}
operator bool() const
{
return _ptr != NULL;
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( int index ) const
{
return _ptr[ index ];
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( unsigned int index ) const
{
return _ptr[ index ];
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( long index ) const
{
return _ptr[ index ];
}
// there are a number of places where we allocate a block intended to be accessed as
// an array of elements, so this operator allows us to treat the memory as such.
T& operator[]( unsigned short index ) const
{
return _ptr[ index ];
}
// access elements of a structure through the auto ptr
T* const operator->( void ) const
{
return _ptr;
}
// value from reference operator (i.e., i = *(&i); or *i = blah;)
T& operator*() const
{
return *_ptr;
}
// allow the use of the address-of operator to simulate a **.
// Note: this operator conflicts with storing these within an STL container. If you need
// to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp()
T** operator&( void )
{
return &_ptr;
}
protected:
sqlsrv_auto_ptr( T* ptr ) :
_ptr( ptr )
{
}
sqlsrv_auto_ptr( sqlsrv_auto_ptr& src )
{
if( _ptr ) {
static_cast<Subclass*>(this)->reset( src._ptr );
}
src.transferred();
}
// assign a new pointer to the auto_ptr. It will free the previous memory block
// because ownership is deemed finished.
T* operator=( T* ptr )
{
static_cast<Subclass*>( this )->reset( ptr );
return ptr;
}
T* _ptr;
};
// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer
// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is
// freed until the variable is destroyed (out of scope) or ownership is transferred using the function
// "transferred".
// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function.
template <typename T>
class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> > {
public:
sqlsrv_malloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( NULL )
{
}
sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( T* ptr = NULL )
{
if( _ptr )
sqlsrv_free( (void*) _ptr );
_ptr = ptr;
}
T* operator=( T* ptr )
{
return sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >::operator=( ptr );
}
void operator=( sqlsrv_malloc_auto_ptr<T>& src )
{
T* p = src.get();
src.transferred();
this->_ptr = p;
}
// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function.
// has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer
void resize( size_t new_size )
{
_ptr = reinterpret_cast<T*>( sqlsrv_realloc( _ptr, new_size ));
}
};
// auto ptr for Zend hash tables. Used to clean up a hash table allocated when
// something caused an early exit from the function. This is used when the hash_table is
// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr.
class hash_auto_ptr : public sqlsrv_auto_ptr<HashTable, hash_auto_ptr> {
public:
hash_auto_ptr( void ) :
sqlsrv_auto_ptr<HashTable, hash_auto_ptr>( NULL )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( HashTable* ptr = NULL )
{
if( _ptr ) {
zend_hash_destroy( _ptr );
FREE_HASHTABLE( _ptr );
}
_ptr = ptr;
}
HashTable* operator=( HashTable* ptr )
{
return sqlsrv_auto_ptr<HashTable, hash_auto_ptr>::operator=( ptr );
}
private:
hash_auto_ptr( HashTable const& hash );
hash_auto_ptr( hash_auto_ptr const& hash );
};
// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr.
// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed
// (out of scope) or ownership is transferred using the function "transferred".
class zval_auto_ptr : public sqlsrv_auto_ptr<zval, zval_auto_ptr> {
public:
zval_auto_ptr( void )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( zval* ptr = NULL )
{
if( _ptr )
zval_ptr_dtor( &_ptr );
_ptr = ptr;
}
zval* operator=( zval* ptr )
{
return sqlsrv_auto_ptr<zval, zval_auto_ptr>::operator=( ptr );
}
#if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3)
operator zval_gc_info*( void )
{
return reinterpret_cast<zval_gc_info*>(_ptr);
}
#endif
private:
zval_auto_ptr( const zval_auto_ptr& src );
};
#pragma pop_macro( "max" )
//*********************************************************************************************************************************
// sqlsrv_error
//*********************************************************************************************************************************
// *** PHP specific errors ***
// sqlsrv errors are held in a structure of this type used by the driver handle_error functions
// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage
// into the error message before returning it.
// base class which can be instatiated with aggregates (see error constants)
struct sqlsrv_error_const {
SQLCHAR* sqlstate;
SQLCHAR* native_message;
SQLINTEGER native_code;
bool format;
};
// subclass which is used by the core layer to instantiate ODBC errors
struct sqlsrv_error : public sqlsrv_error_const {
sqlsrv_error( void )
{
sqlstate = NULL;
native_message = NULL;
native_code = -1;
format = false;
}
sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false )
{
sqlstate = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 ));
strcpy_s( reinterpret_cast<char*>( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast<const char*>( sql_state ));
strcpy_s( reinterpret_cast<char*>( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>( message ));
native_code = code;
format = printf_format;
}
sqlsrv_error( sqlsrv_error_const const& prototype )
{
sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format );
}
~sqlsrv_error( void )
{
if( sqlstate != NULL ) {
sqlsrv_free( sqlstate );
}
if( native_message != NULL ) {
sqlsrv_free( native_message );
}
}
};
// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete
class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr > {
public:
sqlsrv_error_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >( NULL )
{
}
sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >( (sqlsrv_error_auto_ptr&) src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( sqlsrv_error* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_error();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_error* operator=( sqlsrv_error* ptr )
{
return sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >::operator=( ptr );
}
// unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much
// sense. Only the last one would have anything in it.
void operator=( sqlsrv_error_auto_ptr& src )
{
sqlsrv_error* p = src.get();
src.transferred();
this->_ptr = p;
}
};
//*********************************************************************************************************************************
// Context
//*********************************************************************************************************************************
class sqlsrv_context;
struct sqlsrv_conn;
// error_callback
// a driver specific callback for processing errors.
// ctx - the context holding the handles
// sqlsrv_error_code - specific error code to return.
typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args );
// sqlsrv_context
// a context holds relevant information to be passed with a connection and statement objects.
class sqlsrv_context {
public:
sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) :
handle_( SQL_NULL_HANDLE ),
handle_type_( type ),
err_( e ),
name_( NULL ),
driver_( drv ),
last_error_(),
encoding_( encoding )
{
}
sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) :
handle_( h ),
handle_type_( t ),
err_( e ),
name_( NULL ),
driver_( drv ),
last_error_(),
encoding_( encoding )
{
}
sqlsrv_context( sqlsrv_context const& ctx ) :
handle_( ctx.handle_ ),
handle_type_( ctx.handle_type_ ),
err_( ctx.err_ ),
name_( ctx.name_ ),
driver_( ctx.driver_ ),
last_error_( ctx.last_error_ )
{
}
void set_func( const char* f )
{
name_ = f;
}
void set_last_error( sqlsrv_error_auto_ptr& last_error )
{
last_error_ = last_error;
}
sqlsrv_error_auto_ptr& last_error( void )
{
return last_error_;
}
// since the primary responsibility of a context is to hold an ODBC handle, we
// provide these convenience operators for using them interchangeably
operator SQLHANDLE ( void ) const
{
return handle_;
}
error_callback error_handler( void ) const
{
return err_;
}
SQLHANDLE handle( void ) const
{
return handle_;
}
SQLSMALLINT handle_type( void ) const
{
return handle_type_;
}
const char* func( void ) const
{
return name_;
}
void* driver( void ) const
{
return driver_;
}
void set_driver( void* driver )
{
this->driver_ = driver;
}
void invalidate( void )
{
if( handle_ != SQL_NULL_HANDLE ) {
::SQLFreeHandle( handle_type_, handle_ );
}
handle_ = SQL_NULL_HANDLE;
}
bool valid( void )
{
return handle_ != SQL_NULL_HANDLE;
}
SQLSRV_ENCODING encoding( void ) const
{
return encoding_;
}
void set_encoding( SQLSRV_ENCODING e )
{
encoding_ = e;
}
private:
SQLHANDLE handle_; // ODBC handle for this context
SQLSMALLINT handle_type_; // type of the ODBC handle
const char* name_; // function name currently executing this context
error_callback err_; // driver error callback if error occurs in core layer
void* driver_; // points back to the driver for PDO
sqlsrv_error_auto_ptr last_error_; // last error that happened on this object
SQLSRV_ENCODING encoding_; // encoding of the context
};
const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista
// maps an IANA encoding to a code page
struct sqlsrv_encoding {
const char* iana;
unsigned int iana_len;
unsigned int code_page;
bool not_for_connection;
sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ):
iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn )
{
}
};
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// variables set during initialization
extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in
extern HashTable* g_encodings; // encodings supported by this driver
void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC );
void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp );
// environment context used by sqlsrv_connect for when a connection error occurs.
struct sqlsrv_henv {
sqlsrv_context ctx;
sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) :
ctx( handle, SQL_HANDLE_ENV, e, drv )
{
}
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
// supported server versions (determined at connection time)
enum SERVER_VERSION {
SERVER_VERSION_UNKNOWN = -1,
SERVER_VERSION_2000 = 8,
SERVER_VERSION_2005,
SERVER_VERSION_2008, // use this for anything 2008 or later
};
// forward decl
struct sqlsrv_stmt;
struct stmt_option;
// *** connection resource structure ***
// this is the resource structure returned when a connection is made.
struct sqlsrv_conn : public sqlsrv_context {
// instance variables
SERVER_VERSION server_version; // version of the server that we're connected to
// initialize with default values
sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
}
// sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be
// called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating
// the sqlsrv_conn with a connection.
};
enum SQLSRV_STMT_OPTIONS {
SQLSRV_STMT_OPTION_INVALID,
SQLSRV_STMT_OPTION_QUERY_TIMEOUT,
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
SQLSRV_STMT_OPTION_SCROLLABLE,
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
// Driver specific connection options
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
};
namespace ODBCConnOptions {
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char LoginTimeout[] = "LoginTimggeout";
const char MARS_ODBC[] = "MARS_Connection";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
const char QuotedId[] = "QuotedId";
const char TraceFile[] = "TraceFile";
const char TraceOn[] = "TraceOn";
const char TrustServerCertificate[] = "TrustServerCertificate";
const char TransactionIsolation[] = "TransactionIsolation";
const char WSID[] = "WSID";
const char UID[] = "UID";
const char PWD[] = "PWD";
const char SERVER[] = "Server";
}
enum SQLSRV_CONN_OPTIONS {
SQLSRV_CONN_OPTION_INVALID,
SQLSRV_CONN_OPTION_APP,
SQLSRV_CONN_OPTION_CHARACTERSET,
SQLSRV_CONN_OPTION_CONN_POOLING,
SQLSRV_CONN_OPTION_DATABASE,
SQLSRV_CONN_OPTION_ENCRYPT,
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
SQLSRV_CONN_OPTION_MARS,
SQLSRV_CONN_OPTION_QUOTED_ID,
SQLSRV_CONN_OPTION_TRACE_FILE,
SQLSRV_CONN_OPTION_TRACE_ON,
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
SQLSRV_CONN_OPTION_WSID,
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
// Driver specific connection options
SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000,
};
#define NO_ATTRIBUTE -1
// type of connection attributes
enum CONN_ATTR_TYPE {
CONN_ATTR_INT,
CONN_ATTR_BOOL,
CONN_ATTR_STRING,
CONN_ATTR_INVALID,
};
// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or
// sets an attribute)
struct connection_option {
// the name of the option as passed in by the user
const char * sqlsrv_name;
unsigned int sqlsrv_len;
unsigned int conn_option_key;
// the name of the option in the ODBC connection string
const char * odbc_name;
unsigned int odbc_len;
enum CONN_ATTR_TYPE value_type;
// process the connection type
// return whether or not the function was successful in processing the connection option
void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC );
};
// connection attribute functions
template <unsigned int Attr>
struct str_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )),
Z_STRLEN_P( value ) TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
};
// simply add the parsed value to the connection string
struct conn_str_append_func {
static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC );
};
struct conn_null_func {
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC );
// *** connection functions ***
sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
const char* server, const char* uid, const char* pwd,
HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[],
void* driver, const char* driver_func TSRMLS_DC );
void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC );
void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval* server_info TSRMLS_DC );
void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC );
void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC );
bool core_is_conn_opt_value_escaped( const char* value, int value_len );
int core_str_zval_is_true( zval* str_zval );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_functor {
virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC );
};
struct stmt_option_query_timeout : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
struct stmt_option_send_at_exec : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
struct stmt_option_buffered_query_limit : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC );
};
// used to hold the table for statment options
struct stmt_option {
const char * name; // name of the statement option
unsigned int name_len; // name length
unsigned int key;
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;
int stmt_index;
sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) :
stream_z( str_z ), encoding( enc )
{
}
sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL )
{
}
};
// close any active stream
void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC );
extern php_stream_wrapper g_sqlsrv_stream_wrapper;
// resource constants used when registering the stream type with PHP
#define SQLSRV_STREAM_WRAPPER "sqlsrv"
#define SQLSRV_STREAM "sqlsrv_stream"
// holds the output parameter information. Strings also need the encoding and other information for
// after processing. Only integer, float, and strings are allowable output parameters.
struct sqlsrv_output_param {
zval* param_z;
SQLSRV_ENCODING encoding;
int param_num; // used to index into the ind_or_len of the statement
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
bool is_bool;
// string output param constructor
sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) :
param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false )
{
}
// every other type output parameter constructor
sqlsrv_output_param( zval* p_z, int num, bool is_bool ) :
param_z( p_z ),
param_num( num ),
encoding( SQLSRV_ENCODING_INVALID ),
original_buffer_len( -1 ),
is_bool( is_bool )
{
}
};
// forward decls
struct sqlsrv_result_set;
// *** Statement resource structure ***
struct sqlsrv_stmt : public sqlsrv_context {
void free_param_data( TSRMLS_D );
virtual void new_result_set( TSRMLS_D );
sqlsrv_conn* conn; // Connection that created this statement
bool executed; // Whether the statement has been executed yet (used for error messages)
bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row
sqlsrv_result_set* current_results; // Current result set
SQLULEN cursor_type; // Type of cursor for the current result set
bool has_rows; // Has_rows is set if there are actual rows in the row set
bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called
int last_field_index; // last field retrieved by core_sqlsrv_get_field
bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the
// last results
unsigned long query_timeout; // maximum allowed statement execution time
unsigned long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
// holds output pointers for SQLBindParameter
// We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving
// memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold
std::deque<SQLLEN> param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter
zval* param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP
zval* output_params; // hold all the output parameters
zval* param_streams; // track which streams to send data to the server
zval* param_datetime_buffers; // datetime strings to be converted back to DateTime objects
bool send_streams_at_exec; // send all stream data right after execution before returning
sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter
unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string
// to the server)
zval* field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals
zval* active_stream; // the currently active stream reading data from the database
sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC );
virtual ~sqlsrv_stmt( void );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0;
};
// *** field metadata struct ***
struct field_meta_data {
sqlsrv_malloc_auto_ptr<SQLCHAR> field_name;
SQLSMALLINT field_name_len;
SQLSMALLINT field_type;
SQLULEN field_size;
SQLULEN field_precision;
SQLSMALLINT field_scale;
SQLSMALLINT field_is_nullable;
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
field_scale (0), field_is_nullable(0)
{
}
~field_meta_data()
{
}
};
// *** statement constants ***
// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value
const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff;
const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int)
// uninitialized query timeout value
const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff;
// special buffered query constant
const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant
// factory to create a statement
typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC );
// *** statement functions ***
sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht,
const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC );
void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z,
SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size,
SQLSMALLINT decimal_digits TSRMLS_DC );
void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 );
field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC );
bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string,
__out void** field_value, __out SQLLEN* field_length, bool cache_field,
__out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC );
bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true );
void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z TSRMLS_DC );
void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC );
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC );
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC );
//*********************************************************************************************************************************
// Result Set
//*********************************************************************************************************************************
// Abstract the result set so that a result set can either be used as is from ODBC or buffered.
// This is not a complete abstraction of a result set. Only enough is abstracted to allow for
// information and capabilities normally not available when a result set is not buffered
// (e.g., forward only vs buffered means row count is available and cursor movement is possible).
// Otherwise, normal ODBC calls are still valid and should be used to get information about the
// result set (e.g., SQLNumResultCols).
struct sqlsrv_result_set {
sqlsrv_stmt* odbc;
explicit sqlsrv_result_set( sqlsrv_stmt* );
virtual ~sqlsrv_result_set( void ) { }
virtual bool cached( int field_index ) = 0;
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0;
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )= 0;
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0;
virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0;
virtual SQLLEN row_count( TSRMLS_D ) = 0;
};
struct sqlsrv_odbc_result_set : public sqlsrv_result_set {
explicit sqlsrv_odbc_result_set( sqlsrv_stmt* );
virtual ~sqlsrv_odbc_result_set( void );
virtual bool cached( int field_index ) { return false; }
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC );
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC );
virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number );
virtual SQLLEN row_count( TSRMLS_D );
private:
// prevent invalid instantiations and assignments
sqlsrv_odbc_result_set( void );
sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& );
sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& );
};
struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
struct meta_data {
SQLSMALLINT type;
SQLSMALLINT c_type; // convenience
SQLULEN offset; // in bytes
SQLULEN length; // in bytes
SQLSMALLINT scale;
static const SQLULEN SIZE_UNKNOWN = 0;
};
// default maximum amount of memory that a buffered query can consume
#define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings
static const unsigned long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB
static const long BUFFERED_QUERY_LIMIT_INVALID = 0;
explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC );
virtual ~sqlsrv_buffered_result_set( void );
virtual bool cached( int field_index ) { return true; }
virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC );
virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC );
virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC );
virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number );
virtual SQLLEN row_count( TSRMLS_D );
// buffered result set specific
SQLSMALLINT column_count( void )
{
return col_count;
}
struct meta_data& col_meta_data( SQLSMALLINT i )
{
return meta[i];
}
private:
// prevent invalid instantiations and assignments
sqlsrv_buffered_result_set( void );
sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& );
sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& );
HashTable* cache; // rows of data kept in index based hash table
SQLSMALLINT col_count; // number of columns in the current result set
meta_data* meta; // metadata for fields in the cache
SQLLEN current; // 1 based, 0 means before first row
sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here
SQLUSMALLINT last_field_index; // the last field data retrieved from
SQLLEN read_so_far; // position within string to read from (for partial reads of strings)
sqlsrv_malloc_auto_ptr<SQLCHAR> temp_string; // temp buffer to hold a converted field while in use
SQLLEN temp_length; // number of bytes in the temp conversion buffer
typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t;
// two dimentional sparse matrix that holds the [from][to] functions that do conversions
static conv_matrix_t conv_matrix;
// string conversion functions
SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// long conversion functions
SQLRETURN to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length );
SQLRETURN long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// double conversion functions
SQLRETURN to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length );
SQLRETURN double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// string to number conversion functions
// Future: See if these can be converted directly to template member functions
SQLRETURN string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
SQLRETURN wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
__out SQLLEN* out_buffer_length );
// utility functions for conversions
unsigned char* get_row( void );
};
//*********************************************************************************************************************************
// Utility
//*********************************************************************************************************************************
// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler.
// We use this since the unused variables are buried in the PHP_FUNCTION macro.
#define SQLSRV_UNUSED( var ) var;
// do a heap check in debug mode, but only print errors, not all of the allocations
#define MEMCHECK_SILENT 1
// utility functions shared by multiple callers across files
bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len);
bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen );
wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string,
unsigned int mbcs_len, __out unsigned int* utf16_len );
//*********************************************************************************************************************************
// Error handling routines and Predefined Errors
//*********************************************************************************************************************************
enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_ODBC,
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_ZEND_HASH,
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
SQLSRV_ERROR_ZEND_STREAM,
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
SQLSRV_ERROR_FETCH_PAST_END,
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
SQLSRV_ERROR_NO_FIELDS,
SQLSRV_ERROR_INVALID_TYPE,
SQLSRV_ERROR_FETCH_NOT_CALLED,
SQLSRV_ERROR_NO_DATA,
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED,
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
SQLSRV_ERROR_INVALID_CONNECTION_KEY,
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
SQLSRV_ERROR_INVALID_OPTION_KEY,
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
SQLSRV_ERROR_DATETIME_CONVERSION_FAILED,
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
SQLSRV_ERROR_STREAM_CREATE,
SQLSRV_ERROR_MARS_OFF,
SQLSRV_ERROR_FIELD_INDEX_ERROR,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
};
// the message returned by ODBC Driver 11 for SQL Server
const char CONNECTION_BUSY_ODBC_ERROR[] = "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for "
"another command";
// SQLSTATE for all internal errors
extern SQLCHAR IMSSP[];
// SQLSTATE for all internal warnings
extern SQLCHAR SSPWARN[];
// flags passed to sqlsrv_errors to filter its return values
enum error_handling_flags {
SQLSRV_ERR_ERRORS,
SQLSRV_ERR_WARNINGS,
SQLSRV_ERR_ALL
};
// *** internal error macros and functions ***
// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the
// errno is 1 based. It returns it as an array with 3 members:
// 1/SQLSTATE) sqlstate
// 2/code) driver specific error code
// 3/message) driver specific error message
// The fetch type determines if the indices are numeric, associative, or both.
bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, __out sqlsrv_error_auto_ptr& error,
logging_severity severity TSRMLS_DC );
// format and return a driver specfic error
void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error,
sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args );
// return the message for the HRESULT returned by GetLastError. Some driver errors use this to
// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails.
const char* get_last_error_message( DWORD last_error = 0 );
// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer
DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... );
// convenience functions that overload either a reference or a pointer so we can use
// either in the CHECK_* functions.
inline bool call_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... )
{
va_list print_params;
va_start( print_params, warning );
bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params );
va_end( print_params );
return ignored;
}
inline bool call_error_handler( sqlsrv_context* ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... )
{
va_list print_params;
va_start( print_params, warning );
bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params );
va_end( print_params );
return ignored;
}
// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which
// we don't want on a web server
#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( SQLCHAR* state )
{
#if defined(ZEND_DEBUG)
if( state == NULL || strlen( reinterpret_cast<char*>( state )) != 5 ) { \
DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \
}
#endif
return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4');
}
// Macros for handling errors. These macros are simplified if statements that take boilerplate
// code down to a single line to avoid distractions in the code.
#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \
bool flag##unique = (condition); \
bool ignored##unique = true; \
if (flag##unique) { \
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, NULL, __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, NULL 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( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC )
{
// We check for the 'connection busy' error caused by having MultipleActiveResultSets off
// and return a more helpful message prepended to the ODBC errors if that error occurs
if( !SQL_SUCCEEDED( r )) {
SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLSMALLINT len = 0;
SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT,
err_msg, SQL_MAX_MESSAGE_LENGTH, &len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
if(( len == sizeof( CONNECTION_BUSY_ODBC_ERROR ) - 1 ) &&
!strcmp( reinterpret_cast<const char*>( err_msg ), CONNECTION_BUSY_ODBC_ERROR )) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF );
}
}
}
// *** ODBC wrappers ***
// wrap the ODBC functions to throw exceptions rather than use the return value to signal errors
// some of the signatures have been altered to be more convenient since the return value is no longer
// required to return the status of the call (e.g., SQLNumResultCols).
// These functions take the sqlsrv_context type. However, since the error handling code can alter
// the context to hold the error, they are not passed as const.
inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
__out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length TSRMLS_DC )
{
SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier,
diag_info_buffer, buffer_length, out_buffer_length );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
return r;
}
inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle,
__out_ecount(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr );
CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) {
throw CoreException();
}
}
inline void SQLBindParameter( sqlsrv_stmt* stmt,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
__inout SQLPOINTER ParameterValuePtr,
SQLLEN BufferLength,
__inout SQLLEN * StrLen_Or_IndPtr
TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize,
DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier,
__out SQLPOINTER field_type_char, SQLSMALLINT buffer_length,
__out SQLSMALLINT* out_buffer_length, __out SQLLEN* field_type_num TSRMLS_DC )
{
SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char,
buffer_length, out_buffer_length, field_type_num );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, __out_z SQLCHAR* col_name, SQLSMALLINT col_name_length,
__out SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, __out SQLULEN* col_size,
__out SQLSMALLINT* decimal_digits, __out SQLSMALLINT* nullable TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out,
data_type, col_size, decimal_digits, nullable);
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC )
{
SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
// SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success
inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC )
{
SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast<SQLCHAR*>( sql ), SQL_NTS );
check_for_mars_error( stmt, r TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast<SQLWCHAR*>( wsql ), SQL_NTS );
check_for_mars_error( stmt, r TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
// SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success
inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLExecute( stmt->handle() );
check_for_mars_error( stmt, r TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC )
{
SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
// wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine
inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {}
}
inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type,
__out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
bool handle_warning TSRMLS_DC )
{
SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length );
if( r == SQL_NO_DATA )
return r;
CHECK_SQL_ERROR( r, stmt ) {
throw CoreException();
}
if( handle_warning ) {
CHECK_SQL_WARNING_AS_ERROR( r, stmt ) {
throw CoreException();
}
}
return r;
}
inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, __out SQLPOINTER info_value, SQLSMALLINT buffer_len,
__out SQLSMALLINT* str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLGetTypeInfo( stmt->handle(), data_type );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
// SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set.
inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r = ::SQLMoreResults( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
SQLSMALLINT num_cols;
r = ::SQLNumResultCols( stmt->handle(), &num_cols );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return num_cols;
}
// SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more
// parameters or when the parameters are all processed.
inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, __out SQLPOINTER* value_ptr_ptr TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLParamData( stmt->handle(), value_ptr_ptr );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLPrepareW( stmt->handle(), sql, sql_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r;
SQLLEN rows_affected;
r = ::SQLRowCount( stmt->handle(), &rows_affected );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return rows_affected;
}
inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
}
inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
}
inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC )
{
SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
// *** zend wrappers ***
// exception thrown when a zend function wrapped here fails.
// wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw
// exceptions when an error occurs. They are prefaced with sqlsrv_<zend_function_name> because many of the zend functions are
// actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system.
// If there is a zend function in the source that isn't found here, it is because it returns void and there is no error
// that can be thrown from it.
inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, unsigned int index, zval* value TSRMLS_DC)
{
int zr = ::add_index_zval( array, index, value );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC)
{
int zr = ::add_next_index_zval( array, value );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, char* key TSRMLS_DC )
{
int zr = ::add_assoc_null( array_z, key );
CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, char* key, long val TSRMLS_DC )
{
int zr = ::add_assoc_long( array_z, key, val );
CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, char* key, char* val, bool duplicate TSRMLS_DC )
{
int zr = ::add_assoc_string( array_z, key, val, duplicate );
CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_array_init( sqlsrv_context& ctx, __out zval* new_array TSRMLS_DC)
{
int zr = ::array_init( new_array );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval** stream_z TSRMLS_DC )
{
// this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment
php_stream_from_zval_no_verify( stream, stream_z );
CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_get_current_data( sqlsrv_context& ctx, HashTable* ht, __out void** output_data TSRMLS_DC )
{
int zr = ::zend_hash_get_current_data( ht, output_data );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, int index TSRMLS_DC )
{
int zr = ::zend_hash_index_del( ht, index );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, unsigned long index, void* data,
uint data_size TSRMLS_DC )
{
int zr = ::zend_hash_index_update( ht, index, data, data_size, NULL );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, void* data,
uint data_size TSRMLS_DC )
{
int zr = ::zend_hash_next_index_insert( ht, data, data_size, NULL );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_init( sqlsrv_context& ctx, HashTable* ht, unsigned int initial_size, hash_func_t hash_fn,
dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC )
{
int zr = ::zend_hash_init( ht, initial_size, hash_fn, dtor_fn, persistent );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, char* key, unsigned int key_len, void** data,
unsigned int data_size, void **pDest TSRMLS_DC )
{
int zr = ::zend_hash_add( ht, key, key_len, data, data_size, pDest );
CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) {
throw CoreException();
}
}
template <typename Statement>
sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC )
{
return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC );
}
template <typename Connection>
sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC )
{
return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC );
}
} // namespace core
#endif // CORE_SQLSRV_H

View file

@ -1,2436 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stmt.cpp
//
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
namespace {
// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the
// results of every field request, and if it is out of order, we cache those for preceding fields.
struct field_cache {
void* value;
SQLLEN len;
sqlsrv_phptype type;
field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t )
: type( t )
{
// if the value is NULL, then just record a NULL pointer
if( field_value != NULL ) {
value = sqlsrv_malloc( field_len );
memcpy( value, field_value, field_len );
len = field_len;
}
else {
value = NULL;
len = 0;
}
}
// no destructor because we don't want to release the memory when it goes out of scope, but instead we
// rely on the hash table destructor to free the memory
};
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads
const unsigned int UTF8_MIDBYTE_MASK = 0xc0;
const unsigned int UTF8_MIDBYTE_TAG = 0x80;
const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0;
const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0;
const unsigned int UTF8_3BYTESEQ_TAG = 0xe0;
const unsigned int UTF8_4BYTESEQ_TAG = 0xf0;
const unsigned int UTF8_NBYTESEQ_MASK = 0xf0;
// constants used to convert from a DateTime object to a string which is sent to the server.
// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx
namespace DateTime {
const char DATETIME_CLASS_NAME[] = "DateTime";
const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1;
const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P";
const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT );
const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u";
const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT );
const char DATE_FORMAT[] = "Y-m-d";
const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT );
}
// *** internal functions ***
// Only declarations are put here. Functions contain the documentation they need at their definition sites.
void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC );
size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC );
bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC );
bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z );
void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype
sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC );
// returns the ODBC C type constant that matches the PHP type and encoding given
SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC );
void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC );
// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate)
void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLSMALLINT& sql_type TSRMLS_DC );
void field_cache_dtor( void* data );
void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC );
void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type,
__out void** field_value, __out SQLLEN* field_len TSRMLS_DC );
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC );
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type );
// assure there is enough space for the output parameter string
void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding,
SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer,
SQLLEN& buffer_len TSRMLS_DC );
void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC );
// send all the stream data
void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC );
// called when a bound output string parameter is to be destroyed
void sqlsrv_output_param_dtor( void* data );
// called when a bound stream parameter is to be destroyed.
void sqlsrv_stream_dtor( void* data );
bool is_streamable_type( SQLINTEGER sql_type );
}
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) :
sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ),
conn( c ),
executed( false ),
past_fetch_end( false ),
current_results( NULL ),
cursor_type( SQL_CURSOR_FORWARD_ONLY ),
has_rows( false ),
fetch_called( false ),
last_field_index( -1 ),
past_next_result_end( false ),
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
send_streams_at_exec( true ),
current_stream( NULL, SQLSRV_ENCODING_DEFAULT ),
current_stream_read( 0 ),
query_timeout( QUERY_TIMEOUT_INVALID ),
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
active_stream( NULL )
{
// initialize the input string parameters array (which holds zvals)
MAKE_STD_ZVAL( param_input_strings );
core::sqlsrv_array_init( *conn, param_input_strings TSRMLS_CC );
// initialize the (input only) stream parameters (which holds sqlsrv_stream structures)
MAKE_STD_ZVAL( param_streams );
Z_TYPE_P( param_streams ) = IS_ARRAY;
ALLOC_HASHTABLE( Z_ARRVAL_P( param_streams ));
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( param_streams ), 5 /* # of buckets */, NULL /*hashfn*/,
sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC );
// initialize the (input only) datetime parameters of converted date time objects to strings
MAKE_STD_ZVAL( param_datetime_buffers );
array_init( param_datetime_buffers );
// initialize the output string parameters (which holds sqlsrv_output_param structures)
MAKE_STD_ZVAL( output_params );
Z_TYPE_P( output_params ) = IS_ARRAY;
ALLOC_HASHTABLE( Z_ARRVAL_P( output_params ));
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( output_params ), 5 /* # of buckets */, NULL /*hashfn*/,
sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC );
// initialize the field cache
MAKE_STD_ZVAL( field_cache );
Z_TYPE_P( field_cache ) = IS_ARRAY;
ALLOC_HASHTABLE( Z_ARRVAL_P( field_cache ));
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( field_cache ), 5 /* # of buckets */, NULL /*hashfn*/,
field_cache_dtor, 0 /*persistent*/ TSRMLS_CC );
}
// desctructor for sqlsrv statement.
sqlsrv_stmt::~sqlsrv_stmt( void )
{
if( active_stream ) {
TSRMLS_FETCH();
close_active_stream( this TSRMLS_CC );
}
// delete any current results
if( current_results ) {
current_results->~sqlsrv_result_set();
efree( current_results );
current_results = NULL;
}
invalidate();
zval_ptr_dtor( &param_input_strings );
zval_ptr_dtor( &output_params );
zval_ptr_dtor( &param_streams );
zval_ptr_dtor( &param_datetime_buffers );
zval_ptr_dtor( &field_cache );
}
// centralized place to release (without destroying the hash tables
// themselves) all the parameter data that accrues during the
// execution phase.
void sqlsrv_stmt::free_param_data( TSRMLS_D )
{
SQLSRV_ASSERT( Z_TYPE_P( param_input_strings ) == IS_ARRAY && Z_TYPE_P( param_streams ) == IS_ARRAY,
"sqlsrv_stmt::free_param_data: Param zvals aren't arrays." );
zend_hash_clean( Z_ARRVAL_P( param_input_strings ));
zend_hash_clean( Z_ARRVAL_P( output_params ));
zend_hash_clean( Z_ARRVAL_P( param_streams ));
zend_hash_clean( Z_ARRVAL_P( param_datetime_buffers ));
zend_hash_clean( Z_ARRVAL_P( field_cache ));
}
// to be called whenever a new result set is created, such as after an
// execute or next_result. Resets the state variables.
void sqlsrv_stmt::new_result_set( TSRMLS_D )
{
this->fetch_called = false;
this->has_rows = false;
this->past_next_result_end = false;
this->past_fetch_end = false;
this->last_field_index = -1;
// delete any current results
if( current_results ) {
current_results->~sqlsrv_result_set();
efree( current_results );
current_results = NULL;
}
// create a new result set
if( cursor_type == SQLSRV_CURSOR_BUFFERED ) {
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC );
}
else {
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this );
}
}
// core_sqlsrv_create_stmt
// Common code to allocate a statement from either driver. Returns a valid driver statement object or
// throws an exception if an error occurs.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// stmt_factory - factory method to create a statement.
// options_ht - A HashTable of user provided options to be set on the statement.
// valid_stmt_opts - An array of valid driver supported statement options.
// err - callback for error handling
// driver - reference to caller
// Return
// Returns the created statement
sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht,
const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC )
{
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
SQLHANDLE stmt_h = SQL_NULL_HANDLE;
try {
core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC );
stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC );
stmt->conn = conn;
// handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free
// in the catch block below.
stmt_h = SQL_NULL_HANDLE;
// process the options array given to core_sqlsrv_prepare.
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
for( zend_hash_internal_pointer_reset( options_ht );
zend_hash_has_more_elements( options_ht ) == SUCCESS;
zend_hash_move_forward( options_ht )) {
char *key = NULL;
unsigned int key_len = 0;
unsigned long index = -1;
zval** value_z = NULL;
int type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &index, 0, NULL );
// The driver layer should ensure a valid key.
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." );
core::sqlsrv_zend_hash_get_current_data( *(stmt->conn), options_ht, (void**) &value_z TSRMLS_CC );
const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC );
// if the key didn't match, then return the error to the script.
// The driver layer should ensure that the key is valid.
DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." );
// perform the actions the statement option needs done.
(*stmt_opt->func)( stmt, stmt_opt, *value_z TSRMLS_CC );
}
zend_hash_internal_pointer_end( options_ht );
}
sqlsrv_stmt* return_stmt = stmt;
stmt.transferred();
return return_stmt;
}
catch( core::CoreException& )
{
if( stmt ) {
conn->set_last_error( stmt->last_error() );
stmt->~sqlsrv_stmt();
}
// if allocating the handle failed before the statement was allocated, free the handle
if( stmt_h != SQL_NULL_HANDLE) {
::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h );
}
throw;
}
catch( ... ) {
DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." );
}
}
// core_sqlsrv_bind_param
// Binds a parameter using SQLBindParameter. It allocates memory and handles other details
// in translating between the driver and ODBC.
// Parameters:
// param_num - number of the parameter, 0 based
// param_z - zval of the parameter
// php_out_type - type to return for output parameter
// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults)
// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults)
// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale
// Return:
// Nothing, though an exception is thrown if an error occurs
// The php type of the parameter is taken from the zval.
// The sql type is given as a hint if the driver provides it.
void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z,
SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size,
SQLSMALLINT decimal_digits TSRMLS_DC )
{
SQLSMALLINT c_type;
SQLPOINTER buffer = NULL;
SQLLEN buffer_len = 0;
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT,
"core_sqlsrv_bind_param: Invalid parameter direction." );
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID,
"core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." );
try {
// check is only < because params are 0 based
CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) {
throw core::CoreException();
}
// resize the statements array of int_ptrs if the parameter isn't already set.
if( stmt->param_ind_ptrs.size() < param_num + 1 ) {
stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA );
}
SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ];
bool zval_was_null = (Z_TYPE_P( param_z ) == IS_NULL);
bool zval_was_bool = (Z_TYPE_P( param_z ) == IS_BOOL);
// if the user asks for for a specific type for input and output, make sure the data type we send matches the data we
// type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so
// we always let that match if they want a string back.
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
bool match = false;
switch( php_out_type ) {
case SQLSRV_PHPTYPE_INT:
if( zval_was_null || zval_was_bool ) {
convert_to_long( param_z );
}
match = Z_TYPE_P( param_z ) == IS_LONG;
break;
case SQLSRV_PHPTYPE_FLOAT:
if( zval_was_null ) {
convert_to_double( param_z );
}
match = Z_TYPE_P( param_z ) == IS_DOUBLE;
break;
case SQLSRV_PHPTYPE_STRING:
// anything can be converted to a string
convert_to_string( param_z );
match = true;
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
SQLSRV_ASSERT( false, "Invalid type for an output parameter." );
break;
default:
SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." );
break;
}
CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) {
throw core::CoreException();
}
}
// if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so
// when the buffer is filled, the type is correct
if( direction == SQL_PARAM_OUTPUT ) {
switch( php_out_type ) {
case SQLSRV_PHPTYPE_INT:
convert_to_long( param_z );
break;
case SQLSRV_PHPTYPE_FLOAT:
convert_to_double( param_z );
break;
case SQLSRV_PHPTYPE_STRING:
convert_to_string( param_z );
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
SQLSRV_ASSERT( false, "Invalid type for an output parameter" );
break;
default:
SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" );
break;
}
}
SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) ||
( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 ||
encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" );
// if the sql type is unknown, then set the default based on the PHP type passed in
if( sql_type == SQL_UNKNOWN_TYPE ) {
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
}
// if the size is unknown, then set the default based on the PHP type passed in
if( column_size == SQLSRV_UNKNOWN_SIZE ) {
default_sql_size_and_scale( stmt, param_num, param_z, encoding, column_size, decimal_digits TSRMLS_CC );
}
// determine the ODBC C type
c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC );
// set the buffer based on the PHP parameter type
switch( Z_TYPE_P( param_z )) {
case IS_NULL:
{
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
ind_ptr = SQL_NULL_DATA;
buffer = NULL;
buffer_len = 0;
}
break;
case IS_BOOL:
case IS_LONG:
{
buffer = &param_z->value;
buffer_len = sizeof( param_z->value.lval );
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ) {
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
sqlsrv_output_param output_param( param_z, param_num, zval_was_bool );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
}
}
break;
case IS_DOUBLE:
{
buffer = &param_z->value;
buffer_len = sizeof( param_z->value.dval );
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ) {
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
sqlsrv_output_param output_param( param_z, param_num, false );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
}
}
break;
case IS_STRING:
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) {
zval_auto_ptr wbuffer_z;
ALLOC_INIT_ZVAL( wbuffer_z );
bool converted = convert_input_param_to_utf16( param_z, wbuffer_z );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
param_num + 1, get_last_error_message() ) {
throw core::CoreException();
}
buffer = Z_STRVAL_P( wbuffer_z );
buffer_len = Z_STRLEN_P( wbuffer_z );
core::sqlsrv_add_index_zval( *stmt, stmt->param_input_strings, param_num, wbuffer_z TSRMLS_CC );
wbuffer_z.transferred();
}
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ) {
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4
// PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion,
// we reallocate the string if it's interned
if( IS_INTERNED( buffer )) {
ZVAL_STRINGL( param_z, static_cast<const char*>(buffer), buffer_len, 1 );
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
}
#endif
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
// convert it to wchar first
if( direction == SQL_PARAM_INPUT_OUTPUT &&
(c_type == SQL_C_WCHAR ||
(c_type == SQL_C_BINARY &&
(sql_type == SQL_WCHAR ||
sql_type == SQL_WVARCHAR ||
sql_type == SQL_WLONGVARCHAR )))) {
bool converted = convert_input_param_to_utf16( param_z, param_z );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
param_num + 1, get_last_error_message() ) {
throw core::CoreException();
}
sqlsrv_free( buffer );
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
ind_ptr = buffer_len;
}
// since this is an output string, assure there is enough space to hold the requested size and
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size,
buffer, buffer_len TSRMLS_CC );
// save the parameter to be adjusted and/or converted after the results are processed
sqlsrv_output_param output_param( param_z, encoding, param_num, buffer_len );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
// For output parameters, if we set the column_size to be same as the buffer_len,
// than if there is a truncation due to the data coming from the server being
// greater than the column_size, we don't get any truncation error. In order to
// avoid this silent truncation, we set the column_size to be "MAX" size for
// string types. This will guarantee that there is no silent truncation for
// output parameters.
if( direction == SQL_PARAM_OUTPUT ) {
switch( sql_type ) {
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
column_size = SQL_SS_LENGTH_UNLIMITED;
break;
default:
break;
}
}
}
break;
case IS_RESOURCE:
{
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
sqlsrv_stream stream_encoding( param_z, encoding );
HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams );
core::sqlsrv_zend_hash_index_update( *stmt, streams_ht, param_num, &stream_encoding, sizeof( stream_encoding )
TSRMLS_CC );
buffer = reinterpret_cast<SQLPOINTER>( param_num );
zval_add_ref( &param_z ); // so that it doesn't go away while we're using it
buffer_len = 0;
ind_ptr = SQL_DATA_AT_EXEC;
}
break;
case IS_OBJECT:
{
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
zval_auto_ptr function_z;
zval_auto_ptr buffer_z;
zval_auto_ptr format_z;
zval* params[1];
bool valid_class_name_found = false;
zend_class_entry *class_entry = zend_get_class_entry( param_z TSRMLS_CC );
while( class_entry != NULL ) {
if( class_entry->name_length == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL &&
stricmp( class_entry->name, DateTime::DATETIME_CLASS_NAME ) == 0 ) {
valid_class_name_found = true;
break;
}
else {
// Check the parent
class_entry = class_entry->parent;
}
}
CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
throw core::CoreException();
}
ALLOC_INIT_ZVAL( buffer_z );
ALLOC_INIT_ZVAL( function_z );
ALLOC_INIT_ZVAL( format_z );
// if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
// sql type, it lacks the timezone.
if( sql_type == SQL_SS_TIMESTAMPOFFSET ) {
ZVAL_STRINGL( format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
DateTime::DATETIMEOFFSET_FORMAT_LEN, 1 /* dup */ );
}
else if( sql_type == SQL_TYPE_DATE ) {
ZVAL_STRINGL( format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN, 1 /* dup */ );
}
else {
ZVAL_STRINGL( format_z, const_cast<char*>( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN,
1 /* dup */);
}
// call the DateTime::format member function to convert the object to a string that SQL Server understands
ZVAL_STRINGL( function_z, "format", sizeof( "format" ) - 1, 1 );
params[0] = format_z;
// This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the
// DateTime object and $format_z is the format string.
int zr = call_user_function( EG( function_table ), &param_z, function_z, buffer_z, 1, params TSRMLS_CC );
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
throw core::CoreException();
}
buffer = Z_STRVAL_P( buffer_z );
zr = add_next_index_zval( stmt->param_datetime_buffers, buffer_z );
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
throw core::CoreException();
}
buffer_len = Z_STRLEN_P( buffer_z );
buffer_z.transferred();
ind_ptr = buffer_len;
break;
}
case IS_ARRAY:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 );
break;
default:
DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. "
"It is the responsibilty of the driver layer to convert a parameter to one of these types." );
break;
}
if( zval_was_null ) {
ind_ptr = SQL_NULL_DATA;
}
core::SQLBindParameter( stmt, param_num + 1, direction, c_type, sql_type, column_size, decimal_digits, buffer, buffer_len,
&ind_ptr TSRMLS_CC );
}
catch( core::CoreException& e ) {
stmt->free_param_data( TSRMLS_C );
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
throw e;
}
}
// core_sqlsrv_execute
// Executes the statement previously prepared
// Parameters:
// stmt - the core sqlsrv_stmt structure that contains the ODBC handle
// Return:
// true if there is data, false if there is not
void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len )
{
try {
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
SQLRETURN r;
if( sql ) {
sqlsrv_malloc_auto_ptr<wchar_t> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( sizeof( wchar_t )));
wsql_string[0] = L'\0';
wsql_len = 0;
}
else {
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
stmt->encoding() );
wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast<const char*>( sql ),
sql_len, &wsql_len );
CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
get_last_error_message() ) {
throw core::CoreException();
}
}
r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC );
}
else {
r = core::SQLExecute( stmt TSRMLS_CC );
}
// if data is needed (streams were bound) and they should be sent at execute time, then do so now
if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) {
send_param_streams( stmt TSRMLS_CC );
}
stmt->new_result_set( TSRMLS_C );
stmt->executed = true;
// if all the data has been sent and no data was returned then finalize the output parameters
if( stmt->send_streams_at_exec && (r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) {
finalize_output_parameters( stmt TSRMLS_CC );
}
}
catch( core::CoreException& e ) {
// if the statement executed but failed in a subsequent operation before returning,
// we need to cancel the statement
if( stmt->executed ) {
SQLCancel( stmt->handle() );
// stmt->executed = false; should this be reset if something fails?
}
throw e;
}
}
// core_sqlsrv_fetch
// Moves the cursor according to the parameters (by default, moves to the next row)
// Parameters:
// stmt - the sqlsrv_stmt of the cursor
// fetch_orientation - method to move the cursor
// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number)
// Returns:
// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the
// user scrolls past a non-scrollable result set
bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC )
{
// pre-condition check
SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE,
"core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." );
try {
// clear the field cache of the previous fetch
zend_hash_clean( Z_ARRVAL_P( stmt->field_cache ));
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) {
throw core::CoreException();
}
SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC );
CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) {
throw core::CoreException();
}
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
// if the statement has rows and is not scrollable but doesn't yet have
// fetch_called, this must be the first time we've called sqlsrv_fetch.
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) {
stmt->fetch_called = true;
return true;
}
// move to the record requested. For absolute records, we use a 0 based offset, so +1 since
// SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided.
SQLRETURN r = stmt->current_results->fetch( fetch_orientation,
( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1
TSRMLS_CC );
if( r == SQL_NO_DATA ) {
// if this is a forward only cursor, mark that we've passed the end so future calls result in an error
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) {
stmt->past_fetch_end = true;
}
return false;
}
// mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved
stmt->fetch_called = true;
stmt->last_field_index = -1;
stmt->has_rows = true; // since we made it this far, we must have at least one row
}
catch (core::CoreException& e) {
throw e;
}
catch ( ... ) {
DIE( "core_sqlsrv_fetch: Unexpected exception occurred." );
}
return true;
}
// Retrieves metadata for a field of a prepared statement.
// Parameters:
// colno - the index of the field for which to return the metadata. columns are 0 based in PDO
// Return:
// A field_meta_data* consisting of the field metadata.
field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC )
{
// pre-condition check
SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." );
sqlsrv_malloc_auto_ptr<field_meta_data> meta_data;
SQLSMALLINT field_name_len = 0;
meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data();
meta_data->field_name = static_cast<SQLCHAR*>( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 ));
try {
core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len,
&(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale),
&(meta_data->field_is_nullable) TSRMLS_CC );
}
catch( core::CoreException& e ) {
throw e;
}
// depending on field type, we add the values into size or precision/scale.
switch( meta_data->field_type ) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_BIT:
case SQL_TINYINT:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_BIGINT:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
{
meta_data->field_precision = meta_data->field_size;
meta_data->field_size = 0;
break;
}
default: {
break;
}
}
// Set the field name lenth
meta_data->field_name_len = field_name_len;
field_meta_data* result_field_meta_data = meta_data;
meta_data.transferred();
return result_field_meta_data;
}
// core_sqlsrv_get_field
// Return the value of a column from ODBC
// Parameters:
// stmt - the sqlsrv_stmt from which to retrieve the column
// field_index - 0 based index for the column to retrieve
// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in
// field_value - pointer to the data retrieved
// field_len - length of the data in the field_value buffer
// Returns:
// Nothing, excpetion thrown if an error occurs
void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string,
__out void** field_value, __out SQLLEN* field_len, bool cache_field,
__out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC )
{
try {
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
// if the field has been retrieved before, return the previous result
field_cache* cached = NULL;
if( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), field_index, (void**) &cached ) == SUCCESS ) {
// the field value is NULL
if( cached->value == NULL ) {
*field_value = NULL;
*field_len = 0;
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; }
}
else {
*field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 );
memcpy( *field_value, cached->value, cached->len );
if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING ) {
// prevent the 'string not null terminated' warning
reinterpret_cast<char*>( *field_value )[ cached->len ] = '\0';
}
*field_len = cached->len;
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( cached->type.typeinfo.type ); }
}
return;
}
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
// Make sure that the statement was executed and not just prepared.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
// may also be retrieved.
if( cache_field && ( field_index - stmt->last_field_index ) >= 2 ) {
sqlsrv_phptype invalid;
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
SQLSRV_ASSERT( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), i, (void**) &cached ) == FAILURE,
"Field already cached." );
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field,
sqlsrv_php_type_out TSRMLS_CC );
// delete the value returned since we only want it cached, not the actual value
if( *field_value ) {
efree( *field_value );
*field_value = NULL;
*field_len = 0;
}
}
}
// If the php type was not specified set the php type to be the default type.
if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) {
// Get the SQL type of the field.
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
// Get the length of the field.
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC );
// Get the corresponding php type from the sql type.
sqlsrv_php_type = stmt->sql_type_to_php_type( sql_field_type, sql_field_len, prefer_string );
}
// Verify that we have an acceptable type to convert.
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
throw core::CoreException();
}
if( sqlsrv_php_type_out != NULL )
*sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( sqlsrv_php_type.typeinfo.type );
// Retrieve the data
core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
// if the user wants us to cache the field, we'll do it
if( cache_field ) {
field_cache cache( *field_value, *field_len, sqlsrv_php_type );
core::sqlsrv_zend_hash_index_update( *stmt, Z_ARRVAL_P( stmt->field_cache ), field_index, &cache,
sizeof( field_cache ) TSRMLS_CC );
}
}
catch( core::CoreException& e) {
throw e;
}
}
// core_sqlsrv_has_any_result
// return if any result set or rows affected message is waiting
// to be consumed and moved over by sqlsrv_next_result.
// Parameters:
// stmt - The statement object on which to check for results.
// Return:
// true if any results are present, false otherwise.
bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC )
{
// Use SQLNumResultCols to determine if we have rows or not.
SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
// use SQLRowCount to determine if there is a rows status waiting
SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC );
return (num_cols != 0) || (rows_affected > 0);
}
// core_sqlsrv_next_result
// Advances to the next result set from the last executed query
// Parameters
// stmt - the sqlsrv_stmt structure
// Returns
// Nothing, exception thrown if problem occurs
void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors )
{
try {
// make sure that the statement has been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) {
throw core::CoreException();
}
close_active_stream( stmt TSRMLS_CC );
SQLRETURN r;
if( throw_on_errors ) {
r = core::SQLMoreResults( stmt TSRMLS_CC );
}
else {
r = SQLMoreResults( stmt->handle() );
}
if( r == SQL_NO_DATA ) {
if( stmt->output_params && finalize_output_params ) {
// if we're finished processing result sets, handle the output parameters
finalize_output_parameters( stmt TSRMLS_CC );
}
// mark we are past the end of all results
stmt->past_next_result_end = true;
return;
}
stmt->new_result_set( TSRMLS_C );
}
catch( core::CoreException& e ) {
SQLCancel( stmt->handle() );
throw e;
}
}
// core_sqlsrv_post_param
// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement
// Parameters:
// stmt - the sqlsrv_stmt structure
// param_num - 0 based index of the parameter
// param_z - parameter value itself.
// Returns:
// Nothing, exception thrown if problem occurs
void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int param_num, zval* param_z TSRMLS_DC )
{
SQLSRV_ASSERT( Z_TYPE_P( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." );
SQLSRV_ASSERT( Z_TYPE_P( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." );
// if the parameter was an input string, delete it from the array holding input parameter strings
if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_input_strings ), param_num )) {
core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_input_strings ), param_num TSRMLS_CC );
}
// if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams
// PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it
// with sqlsrv_send_stream_data.
if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_streams ), param_num )) {
sqlsrv_stream* stream_encoding;
zend_hash_index_find( Z_ARRVAL_P( stmt->param_streams ), param_num, (void**) &stream_encoding );
core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_streams ), param_num TSRMLS_CC );
}
}
//Calls SQLSetStmtAttr to set a cursor.
void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC )
{
try {
switch( cursor_type ) {
case SQL_CURSOR_STATIC:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQL_CURSOR_DYNAMIC:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQL_CURSOR_KEYSET_DRIVEN:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQL_CURSOR_FORWARD_ONLY:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC );
break;
case SQLSRV_CURSOR_BUFFERED:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC );
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
break;
}
stmt->cursor_type = cursor_type;
}
catch( core::CoreException& ) {
throw;
}
}
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
}
core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC );
}
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC )
{
if( limit <= 0 ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
}
stmt->buffered_query_limit = limit;
}
// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout
// which accepts timeout parameter as a long. If the zval is not of type long
// than throws error.
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
try {
// validate the value
if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) {
convert_to_string( value_z );
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) );
}
core_sqlsrv_set_query_timeout( stmt, Z_LVAL_P( value_z ) TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
// Overloaded. Accepts the timeout as a long.
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." );
// set the statement attribute
core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( timeout ), SQL_IS_UINTEGER TSRMLS_CC );
// a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which
// is represented by -1.
long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ );
// set the LOCK_TIMEOUT on the server.
char lock_timeout_sql[ 32 ];
int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d",
lock_timeout );
SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )),
"stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." );
core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC );
stmt->query_timeout = timeout;
}
catch( core::CoreException& ) {
throw;
}
}
void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
{
TSRMLS_C;
// zend_is_true does not fail. It either returns true or false.
stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false;
}
// core_sqlsrv_send_stream_packet
// send a single packet from a stream parameter to the database using
// ODBC. This will also handle the transition between parameters. It
// returns true if it is not done sending, false if it is finished.
// return_value is what should be returned to the script if it is
// given. Any errors that occur are posted here.
// Parameters:
// stmt - query to send the next packet for
// Returns:
// true if more data remains to be sent, false if all data processed
bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
// if there no current parameter to process, get the next one
// (probably because this is the first call to sqlsrv_send_stream_data)
if( stmt->current_stream.stream_z == NULL ) {
if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) {
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
stmt->current_stream_read = 0;
return false;
}
}
try {
// get the stream from the zval we bound
php_stream* param_stream = NULL;
core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, &stmt->current_stream.stream_z TSRMLS_CC );
// if we're at the end, then release our current parameter
if( php_stream_eof( param_stream )) {
// if no data was actually sent prior, then send a NULL
if( stmt->current_stream_read == 0 ) {
// send an empty string, which is what a 0 length does.
char buff[1]; // temp storage to hand to SQLPutData
core::SQLPutData( stmt, buff, 0 TSRMLS_CC );
}
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
stmt->current_stream_read = 0;
}
// read the data from the stream, send it via SQLPutData and track how much we've sent.
else {
char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character
size_t read = php_stream_read( param_stream, buffer, buffer_size );
stmt->current_stream_read += read;
if( read > 0 ) {
// if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
// twice.
// If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
// since all other MBCS supported by SQL Server are 2 byte maximum size.
if( stmt->current_stream.encoding == CP_UTF8 ) {
// the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a
// expansion of 2x the UTF-8 size.
wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS,
buffer, read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t ));
if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) {
// this will calculate how many bytes were cut off from the last UTF-8 character and read that many more
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC );
// read the missing bytes
size_t new_read = php_stream_read( param_stream, static_cast<char*>( buffer ) + read,
need_to_read );
// if the bytes couldn't be read, then we return an error
CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) {
throw core::CoreException();
}
// try the conversion again with the complete character
wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS,
buffer, read + new_read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t ));
// something else must be wrong if it failed
CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) {
throw core::CoreException();
}
}
core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC );
}
else {
core::SQLPutData( stmt, buffer, read TSRMLS_CC );
}
}
}
}
catch( core::CoreException& e ) {
stmt->free_param_data( TSRMLS_C );
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
SQLCancel( stmt->handle() );
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT );
stmt->current_stream_read = 0;
throw e;
}
return true;
}
void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC )
{
TSRMLS_C;
// This implementation should never get called.
DIE( "Not implemented." );
}
void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC )
{
core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC );
}
void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC );
}
void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC );
}
// internal function to release the active stream. Called by each main API function
// that will alter the statement and cancel any retrieval of data from a stream.
void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC )
{
// if there is no active stream, return
if( stmt->active_stream == NULL ) {
return;
}
php_stream* stream = NULL;
// we use no verify since verify would return immediately and we want to assert, not return.
php_stream_from_zval_no_verify( stream, &stmt->active_stream );
SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." );
php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here.
SQLSRV_ASSERT( stmt->active_stream == NULL, "close_active_stream: Active stream not closed." );
}
// local routines not shared by other files (arranged alphabetically)
namespace {
bool is_streamable_type( SQLINTEGER sql_type )
{
switch( sql_type ) {
case SQL_CHAR:
case SQL_WCHAR:
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_SS_XML:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
return true;
}
return false;
}
void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC )
{
try {
switch( sql_type ) {
// for types that are fixed in size or for which the size is unknown, return the display size.
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_GUID:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_REAL:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_BINARY:
case SQL_CHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_SS_XML:
case SQL_SS_UDT:
case SQL_WLONGVARCHAR:
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
{
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC );
break;
}
// for wide char types for which the size is known, return the octet length instead, since it will include the
// the number of bytes necessary for the string, not just the characters
case SQL_WCHAR:
case SQL_WVARCHAR:
{
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC );
break;
}
default:
DIE ( "Unexpected SQL type encountered in calc_string_size." );
}
}
catch( core::CoreException& e ) {
throw e;
}
}
// calculates how many characters were cut off from the end of a buffer when reading
// in UTF-8 encoded text
size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC )
{
const char* last_char = buffer + buffer_end - 1;
size_t need_to_read = 0;
// rewind until we are at the byte that starts the cut off character
while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) {
--last_char;
++need_to_read;
}
// determine how many bytes we need to read in based on the number of bytes in the character
// (# of high bits set) versus the number of bytes we've already read.
switch( *last_char & UTF8_NBYTESEQ_MASK ) {
case UTF8_2BYTESEQ_TAG1:
case UTF8_2BYTESEQ_TAG2:
need_to_read = 1 - need_to_read;
break;
case UTF8_3BYTESEQ_TAG:
need_to_read = 2 - need_to_read;
break;
case UTF8_4BYTESEQ_TAG:
need_to_read = 3 - need_to_read;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION ));
break;
}
return need_to_read;
}
// Caller is responsible for freeing the memory allocated for the field_value.
// The memory allocation has to happen in the core layer because otherwise
// the driver layer would have to calculate size of the field_value
// to decide the amount of memory allocation.
void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype
sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC )
{
try {
close_active_stream( stmt TSRMLS_CC );
// make sure that fetch is called before trying to retrieve.
CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) {
throw core::CoreException();
}
// make sure that fields are not retrieved incorrectly.
CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index,
stmt->last_field_index ) {
throw core::CoreException();
}
switch( sqlsrv_php_type.typeinfo.type ) {
case SQLSRV_PHPTYPE_INT:
{
sqlsrv_malloc_auto_ptr<long> field_value_temp;
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
field_len, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
if( *field_len == SQL_NULL_DATA ) {
*field_value = NULL;
break;
}
*field_value = field_value_temp;
field_value_temp.transferred();
break;
}
case SQLSRV_PHPTYPE_FLOAT:
{
sqlsrv_malloc_auto_ptr<double> field_value_temp;
field_value_temp = static_cast<double*>( sqlsrv_malloc( sizeof( double )));
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ),
field_len, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( *field_len == SQL_NULL_DATA ) {
*field_value = NULL;
break;
}
*field_value = field_value_temp;
field_value_temp.transferred();
break;
}
case SQLSRV_PHPTYPE_STRING:
{
get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
break;
}
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and
// convert it to a DateTime object and return the created object
case SQLSRV_PHPTYPE_DATETIME:
{
char field_value_temp[ MAX_DATETIME_STRING_LEN ];
zval_auto_ptr field_value_temp_z;
zval_auto_ptr return_value_z;
zval_auto_ptr function_z;
zval* params[1];
ALLOC_INIT_ZVAL( field_value_temp_z );
ALLOC_INIT_ZVAL( function_z );
ALLOC_INIT_ZVAL( return_value_z );
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp,
MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC );
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( *field_len == SQL_NULL_DATA ) {
ZVAL_NULL( return_value_z );
*field_value = reinterpret_cast<void*>( return_value_z.get() );
return_value_z.transferred();
break;
}
// Convert the string date to a DateTime object
ZVAL_STRINGL( field_value_temp_z, field_value_temp, *field_len, 1 );
ZVAL_STRINGL( function_z, "date_create", sizeof("date_create") -1, 1 );
params[0] = field_value_temp_z;
if( call_user_function( EG( function_table ), NULL, function_z, return_value_z, 1,
params TSRMLS_CC ) == FAILURE ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED );
}
*field_value = reinterpret_cast<void*>( return_value_z.get() );
return_value_z.transferred();
break;
}
// create a stream wrapper around the field and return that object to the PHP script. calls to fread
// on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file
// for how these fields are used.
case SQLSRV_PHPTYPE_STREAM:
{
zval_auto_ptr return_value_z;
php_stream* stream = NULL;
sqlsrv_stream* ss = NULL;
ALLOC_INIT_ZVAL( return_value_z );
SQLINTEGER sql_type;
SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
throw core::CoreException();
}
stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL );
CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) {
throw core::CoreException();
}
ss = static_cast<sqlsrv_stream*>( stream->abstract );
ss->stmt = stmt;
ss->field_index = field_index;
ss->sql_type = static_cast<SQLUSMALLINT>( sql_type );
ss->encoding = static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding );
// turn our stream into a zval to be returned
php_stream_to_zval( stream, return_value_z );
// mark this as our active stream
stmt->active_stream = return_value_z;
*field_value = reinterpret_cast<void*>( return_value_z.get() );
return_value_z.transferred();
break;
}
case SQLSRV_PHPTYPE_NULL:
*field_value = NULL;
*field_len = 0;
break;
default:
DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" );
break;
}
// sucessfully retrieved the field, so update our last retrieved field
if( stmt->last_field_index < field_index ) {
stmt->last_field_index = field_index;
}
}
catch( core::CoreException& e ) {
throw e;
}
}
// check_for_next_stream_parameter
// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise
// returns false
bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC )
{
int stream_index = 0;
SQLRETURN r = SQL_SUCCESS;
sqlsrv_stream* stream_encoding;
zval* param_z = NULL;
// get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param
r = core::SQLParamData( stmt, reinterpret_cast<SQLPOINTER*>( &stream_index ) TSRMLS_CC );
// if no more data, we've exhausted the bound parameters, so return that we're done
if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
// we're all done, so return false
return false;
}
HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams );
// pull out the sqlsrv_encoding struct
int zr = zend_hash_index_find( streams_ht, stream_index, (void**) &stream_encoding );
SQLSRV_ASSERT( zr == SUCCESS, "Stream parameter does not exist" ); // if the index isn't in the hash, that's a serious error
param_z = stream_encoding->stream_z;
// make the next stream current
stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding );
stmt->current_stream_read = 0;
// there are more parameters
return true;
}
// utility routine to convert an input parameter from UTF-8 to UTF-16
bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z )
{
SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL,
"convert_input_param_z called with invalid parameter states" );
const char* buffer = Z_STRVAL_P( input_param_z );
int buffer_len = Z_STRLEN_P( input_param_z );
int wchar_size;
// if the string is empty, then just return that the conversion succeeded as
// MultiByteToWideChar will "fail" on an empty string.
if( buffer_len == 0 ) {
ZVAL_STRINGL( converted_param_z, "", 0, 1 );
return true;
}
// if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string
wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), buffer_len, NULL, 0 );
// if there was a problem determining the size of the string, return false
if( wchar_size == 0 ) {
return false;
}
sqlsrv_malloc_auto_ptr<wchar_t> wbuffer;
wbuffer = reinterpret_cast<wchar_t*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) ));
// convert the utf-8 string to a wchar string in the new buffer
int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ),
buffer_len, wbuffer, wchar_size );
// if there was a problem converting the string, then free the memory and return false
if( r == 0 ) {
return false;
}
// null terminate the string, set the size within the zval, and return success
wbuffer[ wchar_size ] = L'\0';
ZVAL_STRINGL( converted_param_z, reinterpret_cast<char*>( wbuffer.get() ),
wchar_size * sizeof( wchar_t ), 0 );
wbuffer.transferred();
return true;
}
// returns the ODBC C type constant that matches the PHP type and encoding given
SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC )
{
SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE;
int php_type = Z_TYPE_P( param_z );
switch( php_type ) {
case IS_NULL:
switch( encoding ) {
// The c type is set to match to the corresponding sql_type. For NULL cases, if the server type
// is a binary type, than the server expects the sql_type to be binary type as well, otherwise
// an error stating "Implicit conversion not allowed.." is thrown by the server.
// For all other server types, setting the sql_type to sql_char works fine.
case SQLSRV_ENCODING_BINARY:
sql_c_type = SQL_C_BINARY;
break;
default:
sql_c_type = SQL_C_CHAR;
break;
}
break;
case IS_BOOL:
case IS_LONG:
sql_c_type = SQL_C_LONG;
break;
case IS_DOUBLE:
sql_c_type = SQL_C_DOUBLE;
break;
case IS_STRING:
case IS_RESOURCE:
switch( encoding ) {
case SQLSRV_ENCODING_CHAR:
sql_c_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_c_type = SQL_C_BINARY;
break;
case CP_UTF8:
sql_c_type = SQL_C_WCHAR;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno );
break;
}
break;
// it is assumed that an object is a DateTime since it's the only thing we support.
// verification that it's a real DateTime object occurs in core_sqlsrv_bind_param.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
sql_c_type = SQL_C_CHAR;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
break;
}
return sql_c_type;
}
// given a zval and encoding, determine the appropriate sql type
void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLSMALLINT& sql_type TSRMLS_DC )
{
sql_type = SQL_UNKNOWN_TYPE;
int php_type = Z_TYPE_P( param_z );
switch( php_type ) {
case IS_NULL:
switch( encoding ) {
// Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases,
// if the server type is a binary type, than the server expects the sql_type to be binary type
// as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the
// server. For all other server types, setting the sql_type to sql_char works fine.
case SQLSRV_ENCODING_BINARY:
sql_type = SQL_BINARY;
break;
default:
sql_type = SQL_CHAR;
break;
}
break;
case IS_BOOL:
case IS_LONG:
sql_type = SQL_INTEGER;
break;
case IS_DOUBLE:
sql_type = SQL_FLOAT;
break;
case IS_RESOURCE:
case IS_STRING:
switch( encoding ) {
case SQLSRV_ENCODING_CHAR:
sql_type = SQL_VARCHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_type = SQL_VARBINARY;
break;
case CP_UTF8:
sql_type = SQL_WVARCHAR;
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno );
break;
}
break;
// it is assumed that an object is a DateTime since it's the only thing we support.
// verification that it's a real DateTime object occurs in the calling function.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
// since these are the date types of the highest precision for their respective server versions
if( stmt->conn->server_version <= SERVER_VERSION_2005 ) {
sql_type = SQL_TYPE_TIMESTAMP;
}
else {
sql_type = SQL_SS_TIMESTAMPOFFSET;
}
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
break;
}
}
// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate)
void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
__out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC )
{
int php_type = Z_TYPE_P( param_z );
column_size = 0;
decimal_digits = 0;
switch( php_type ) {
case IS_NULL:
column_size = 1;
break;
// size is not necessary for these types, they are inferred by ODBC
case IS_BOOL:
case IS_LONG:
case IS_DOUBLE:
case IS_RESOURCE:
break;
case IS_STRING:
{
SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char ));
if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) {
column_size = SQL_SERVER_MAX_TYPE_SIZE;
}
else {
column_size = Z_STRLEN_P( param_z );
}
break;
}
// it is assumed that an object is a DateTime since it's the only thing we support.
// verification that it's a real DateTime object occurs in the calling function.
// we convert the DateTime to a string before sending it to the server.
case IS_OBJECT:
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
// since these are the date types of the highest precision for their respective server versions
if( stmt->conn->server_version <= SERVER_VERSION_2005 ) {
column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION;
decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE;
}
else {
column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION;
decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE;
}
break;
default:
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
break;
}
}
void field_cache_dtor( void* data )
{
field_cache* cache = reinterpret_cast<field_cache*>( data );
if( cache->value )
{
sqlsrv_free( cache->value );
}
}
// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output
// parameters will be present until all results are processed (since output parameters can depend on results
// while being processed). This function updates the lengths of output parameter strings from the ind_ptr
// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary.
// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server
void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC )
{
if( stmt->output_params == NULL )
return;
bool converted = true;
HashTable* params_ht = Z_ARRVAL_P( stmt->output_params );
for( zend_hash_internal_pointer_reset( params_ht );
zend_hash_has_more_elements( params_ht ) == SUCCESS;
zend_hash_move_forward( params_ht ) ) {
sqlsrv_output_param *output_param;
core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &output_param TSRMLS_CC );
switch( Z_TYPE_P( output_param->param_z )) {
case IS_STRING:
{
// adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter
char* str = Z_STRVAL_P( output_param->param_z );
SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ];
if( str_len == SQL_NULL_DATA ) {
ZVAL_NULL( output_param->param_z );
continue;
}
// if there was more to output than buffer size to hold it, then throw a truncation error
int null_size = 0;
switch( output_param->encoding ) {
case SQLSRV_ENCODING_UTF8:
null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16
break;
case SQLSRV_ENCODING_SYSTEM:
null_size = 1;
break;
case SQLSRV_ENCODING_BINARY:
null_size = 0;
break;
default:
SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." );
break;
}
CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt,
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) {
throw core::CoreException();
}
// if it's not in the 8 bit encodings, then it's in UTF-16
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
bool converted = convert_string_from_utf16_inplace( output_param->encoding, &str, str_len );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
// so we do that here if the length of the returned data is less than the original allocation. The
// original allocation null terminates the buffer already.
str[ str_len ] = '\0';
}
// set the string length
ZVAL_STRINGL( output_param->param_z, str, str_len, 0 );
}
break;
case IS_LONG:
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
ZVAL_NULL( output_param->param_z );
}
else if( output_param->is_bool ) {
convert_to_boolean( output_param->param_z );
}
break;
case IS_DOUBLE:
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
ZVAL_NULL( output_param->param_z );
}
break;
default:
DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." );
break;
}
}
// empty the hash table since it's been processed
zend_hash_clean( Z_ARRVAL_P( stmt->output_params ));
return;
}
void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type,
__out void** field_value, __out SQLLEN* field_len TSRMLS_DC )
{
SQLRETURN r;
SQLSMALLINT c_type;
SQLLEN sql_field_type = 0;
SQLSMALLINT extra = 0;
SQLLEN field_len_temp;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
try {
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
}
// Set the C type and account for null characters at the end of the data.
switch( sqlsrv_php_type.typeinfo.encoding ) {
case CP_UTF8:
c_type = SQL_C_WCHAR;
extra = sizeof( SQLWCHAR );
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
extra = 0;
break;
default:
c_type = SQL_C_CHAR;
extra = sizeof( SQLCHAR );
break;
}
// Get the SQL type of the field.
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
// Calculate the field size.
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
// if this is a large type, then read the first few bytes to get the actual length from SQLGetData
if( sql_display_size == 0 || sql_display_size == LONG_MAX ||
sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) {
field_len_temp = INITIAL_FIELD_STRING_LEN;
field_value_temp = static_cast<char*>( sqlsrv_malloc( field_len_temp + extra + 1 ));
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ),
&field_len_temp, false /*handle_warning*/ TSRMLS_CC );
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( field_len_temp == SQL_NULL_DATA ) {
*field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
if( r == SQL_SUCCESS_WITH_INFO ) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
if( is_truncated_warning( state ) ) {
SQLINTEGER dummy_field_len;
// for XML (and possibly other conditions) the field length returned is not the real field length, so
// in every pass, we double the allocation size to retrieve all the contents.
if( field_len_temp == SQL_NO_TOTAL ) {
// reset the field_len_temp
field_len_temp = INITIAL_FIELD_STRING_LEN;
do {
SQLINTEGER initial_field_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
field_len_temp -= initial_field_len;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len,
field_len_temp + extra, &dummy_field_len,
false /*handle_warning*/ TSRMLS_CC );
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
// so we calculate the actual length of the string with that.
if( dummy_field_len != SQL_NO_TOTAL )
field_len_temp += dummy_field_len;
else
field_len_temp += initial_field_len;
if( r == SQL_SUCCESS_WITH_INFO ) {
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
TSRMLS_CC );
}
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
}
else {
// We got the field_len_temp from SQLGetData call.
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
// We have already recieved INITIAL_FIELD_STRING_LEN size data.
field_len_temp -= INITIAL_FIELD_STRING_LEN;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN,
field_len_temp + extra, &dummy_field_len,
true /*handle_warning*/ TSRMLS_CC );
if( dummy_field_len == SQL_NULL_DATA ) {
*field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
field_len_temp += INITIAL_FIELD_STRING_LEN;
}
} // if( is_truncation_warning ( state ) )
else {
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
}
} // if( r == SQL_SUCCESS_WITH_INFO )
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) {
throw core::CoreException ();
}
}
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) {
// only allow binary retrievals for char and binary types. All others get a string converted
// to the encoding type they asked for.
// null terminator
if( c_type == SQL_C_CHAR ) {
sql_display_size += sizeof( SQLCHAR );
}
// For WCHAR multiply by sizeof(WCHAR) and include the null terminator
else if( c_type == SQL_C_WCHAR ) {
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR);
}
field_value_temp = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
// get the data
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size,
&field_len_temp, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException ();
}
if( field_len_temp == SQL_NULL_DATA ) {
*field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) {
throw core::CoreException ();
}
}
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
else {
DIE( "Invalid sql_display_size" );
return; // to eliminate a warning
}
*field_value = field_value_temp;
*field_len = field_len_temp;
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
// operator to set add 1 to fill the null terminator
field_value_temp[field_len_temp] = '\0';
}
catch( core::CoreException& ) {
*field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
throw;
}
catch ( ... ) {
*field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
throw;
}
}
// return the option from the stmt_opts array that matches the key. If no option found,
// NULL is returned.
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC )
{
for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) {
// if we find the key we're looking for, return it
if( key == stmt_opts[ i ].key ) {
return &stmt_opts[ i ];
}
}
return NULL; // no option found
}
// is_fixed_size_type
// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary
bool is_fixed_size_type( SQLINTEGER sql_type )
{
switch( sql_type ) {
case SQL_BINARY:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
case SQL_SS_XML:
case SQL_SS_UDT:
return false;
}
return true;
}
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type )
{
switch( type.typeinfo.type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
{
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
return true;
}
break;
}
}
return false;
}
// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z
// is updated to have the new buffer with the correct size and its reference is incremented. The output
// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and
// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter
void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding,
SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer,
SQLLEN& buffer_len TSRMLS_DC )
{
SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." );
buffer_len = Z_STRLEN_P( param_z );
buffer = Z_STRVAL_P( param_z );
SQLLEN expected_len;
SQLLEN buffer_null_extra;
SQLLEN elem_size;
SQLLEN without_null_len;
// calculate the size of each 'element' represented by column_size. WCHAR is of course 2,
// as is a n(var)char/ntext field being returned as a binary field.
elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1;
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
expected_len = column_size * elem_size + elem_size;
// binary fields aren't null terminated, so we need to account for that in our buffer length calcuations
buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0;
// this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter
without_null_len = column_size * elem_size;
// increment to include the null terminator since the Zend length doesn't include the null terminator
buffer_len += elem_size;
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new
// length.
if( buffer_len < expected_len ) {
SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra,
"Integer overflow/underflow caused a corrupt field length." );
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
// not having a NULL terminator on a string.
buffer = static_cast<char*>( sqlsrv_realloc( buffer, expected_len ));
buffer_len = expected_len; // set the buffer_len to the new allocation size (includes the null terminator taken out below)
// A zval string len doesn't include the null. This calculates the length it should be
// regardless of whether the ODBC type contains the NULL or not.
ZVAL_STRINGL( param_z, reinterpret_cast<char*>( buffer ), without_null_len, 0 );
// null terminate the string to avoid a warning in debug PHP builds
(static_cast<char*>(buffer))[ without_null_len ] = '\0';
}
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
buffer_len -= buffer_null_extra;
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
// than the error 22001 is returned by ODBC.
if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) {
stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra);
}
}
// output parameters have their reference count incremented so that they do not disappear
// while the query is executed and processed. They are saved in the statement so that
// their reference count may be decremented later (after results are processed)
void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC )
{
HashTable* param_ht = Z_ARRVAL_P( stmt->output_params );
int paramno = param.param_num;
core::sqlsrv_zend_hash_index_update( *stmt, param_ht, paramno, &param, sizeof( param )
TSRMLS_CC );
zval_add_ref( &param.param_z ); // we have a reference to the param
}
// send all the stream data
void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC )
{
while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { }
}
// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed
void sqlsrv_output_param_dtor( void* data )
{
sqlsrv_output_param *output_param = reinterpret_cast<sqlsrv_output_param*>( data );
zval_ptr_dtor( &output_param->param_z ); // undo the reference to the string we will no longer hold
}
// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed
void sqlsrv_stream_dtor( void* data )
{
sqlsrv_stream* stream_encoding = reinterpret_cast<sqlsrv_stream*>( data );
zval_ptr_dtor( &stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold
}
}

View file

@ -1,263 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stream.cpp
//
// Contents: Implementation of PHP streams for reading SQL Server data
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <windows.h>
namespace {
// close a stream and free the PHP resources used by it
int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC )
{
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." );
// free the stream resources in the Zend engine
php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM );
// NULL out the stream zval and delete our reference count to it.
ZVAL_NULL( ss->stmt->active_stream );
// there is no active stream
ss->stmt->active_stream = NULL;
sqlsrv_free( ss );
stream->abstract = NULL;
return 0;
}
// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are
// set when sqlsrv_get_field is called by the user specifying which field type they want.
size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, size_t count TSRMLS_DC )
{
SQLINTEGER read = 0;
SQLSMALLINT c_type = SQL_C_CHAR;
char* get_data_buffer = buf;
sqlsrv_malloc_auto_ptr<char> temp_buf;
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." );
try {
if( stream->eof ) {
return 0;
};
switch( ss->encoding ) {
case SQLSRV_ENCODING_CHAR:
c_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
break;
case CP_UTF8:
{
c_type = SQL_C_WCHAR;
count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes
if( count > PHP_STREAM_BUFFER_SIZE ) {
count = PHP_STREAM_BUFFER_SIZE;
}
// use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16
temp_buf = static_cast<char*>( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE ));
get_data_buffer = temp_buf;
break;
}
default:
DIE( "Unknown encoding type when reading from a stream" );
break;
}
SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read );
CHECK_SQL_ERROR( r, ss->stmt ) {
stream->eof = 1;
throw core::CoreException();
}
// if the stream returns either no data, NULL data, or returns data < than the count requested then
// we are at the "end of the stream" so we mark it
if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast<size_t>( read ) <= count && read != SQL_NO_TOTAL )) {
stream->eof = 1;
}
// if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator
// if it's not a binary encoded field
if( r == SQL_SUCCESS_WITH_INFO ) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
if( read == SQL_NO_TOTAL ) {
SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it "
"did not occur." );
}
if( is_truncated_warning( state ) ) {
switch( c_type ) {
// As per SQLGetData documentation, if the length of character data exceeds the BufferLength,
// SQLGetData truncates the data to BufferLength less the length of null-termination character.
case SQL_C_BINARY:
read = count;
break;
case SQL_C_WCHAR:
read = ( count % 2 == 0 ? count - 2 : count - 3 );
break;
case SQL_C_CHAR:
read = count - 1;
break;
default:
DIE( "sqlsrv_stream_read: should have never reached in this switch case.");
break;
}
}
else {
CHECK_SQL_WARNING( r, ss->stmt );
}
}
// if the encoding is UTF-8
if( c_type == SQL_C_WCHAR ) {
count *= 2; // undo the shift to use the full buffer
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
DWORD flags = 0;
// convert to UTF-8
if( g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast<LPCWSTR>( temp_buf.get() ),
read >> 1, buf, count, NULL, NULL );
if( enc_len == 0 ) {
stream->eof = 1;
THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() );
}
read = enc_len;
}
return read;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." );
return 0;
}
}
// function table for stream operations. We only support reading and closing the stream
php_stream_ops sqlsrv_stream_ops = {
NULL,
sqlsrv_stream_read,
sqlsrv_stream_close,
NULL,
SQLSRV_STREAM,
NULL,
NULL,
NULL,
NULL
};
// open a stream and return the sqlsrv_stream_ops function table as part of the
// return value. There is only one valid way to open a stream, using sqlsrv_get_field on
// certain field types. A sqlsrv stream may only be opened in read mode.
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 6
static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in const char*, __in const char* mode,
int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC )
#else
static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in char*, __in char* mode,
int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC )
#endif
{
#if ZEND_DEBUG
SQLSRV_UNUSED( __zend_orig_lineno );
SQLSRV_UNUSED( __zend_orig_filename );
SQLSRV_UNUSED( __zend_lineno );
SQLSRV_UNUSED( __zend_filename );
SQLSRV_UNUSED( __php_stream_call_depth );
#endif
sqlsrv_malloc_auto_ptr<sqlsrv_stream> ss;
ss = static_cast<sqlsrv_stream*>( sqlsrv_malloc( sizeof( sqlsrv_stream )));
memset( ss, 0, sizeof( sqlsrv_stream ));
// check for valid options
if( options != REPORT_ERRORS ) {
php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" );
return NULL;
}
// allocate the stream from PHP
php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode );
if( php_str != NULL ) {
ss.transferred();
}
return php_str;
}
// information structure that contains PHP stream wrapper info. We supply the minimal
// possible, including the open function and the name only.
php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = {
sqlsrv_stream_opener,
NULL,
NULL,
NULL,
NULL,
SQLSRV_STREAM_WRAPPER,
NULL,
NULL,
NULL,
NULL
};
}
// structure used by PHP to get the function table for opening, closing, etc. of the stream
php_stream_wrapper g_sqlsrv_stream_wrapper = {
&sqlsrv_stream_wrapper_ops,
NULL,
0
};

View file

@ -1,358 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_util.cpp
//
// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include <windows.h>
namespace {
// *** internal constants ***
log_callback g_driver_log;
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// buffer used to hold a formatted log message prior to actually logging it.
char last_err_msg[ 2048 ]; // 2k to hold the error messages
// routine used by utf16_string_from_mbcs_string
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string,
unsigned int mbcs_len,
__out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string,
unsigned int utf16_len );
}
// SQLSTATE for all internal errors
SQLCHAR IMSSP[] = "IMSSP";
// SQLSTATE for all internal warnings
SQLCHAR SSPWARN[] = "01SSP";
// write to the php log if the severity and subsystem match the filters currently set in the INI or
// the script (sqlsrv_configure).
void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...)
{
SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." );
va_list args;
va_start( args, msg );
g_driver_log( severity TSRMLS_CC, msg, &args );
va_end( args );
}
void core_sqlsrv_register_logger( log_callback driver_logger )
{
g_driver_log = driver_logger;
}
// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new
// length in the len parameter. If no errors occurred during convertion, true is returned and the original
// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed
// and false is returned.
bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len)
{
SQLSRV_ASSERT( string != NULL && *string != NULL, "String must be specified" );
// for the empty string, we simply returned we converted it
if( len == 0 && *string[0] == '\0' ) {
return true;
}
char* outString = NULL;
SQLINTEGER outLen = 0;
bool result = convert_string_from_utf16( encoding, reinterpret_cast<const wchar_t*>(*string), len / sizeof(wchar_t), &outString, outLen);
if (result)
{
sqlsrv_free( *string );
*string = outString;
len = outLen;
}
return result;
}
bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen )
{
SQLSRV_ASSERT( inString != NULL, "Input string must be specified" );
SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" );
SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" );
if (cchInLen == 0 && inString[0] == L'\0') {
*outString = reinterpret_cast<char*>( sqlsrv_malloc ( 1 ) );
*outString[0] = '\0';
cchOutLen = 0;
return true;
}
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
DWORD flags = 0;
if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
// calculate the number of characters needed
cchOutLen = WideCharToMultiByte( encoding, flags,
inString, cchInLen,
NULL, 0, NULL, NULL );
if( cchOutLen == 0 ) {
return false;
}
// Create a buffer to fit the encoded string
char* newString = reinterpret_cast<char*>( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ ));
int rc = WideCharToMultiByte( encoding, flags,
inString, cchInLen,
newString, cchOutLen, NULL, NULL );
if( rc == 0 ) {
cchOutLen = 0;
sqlsrv_free( newString );
return false;
}
*outString = newString;
newString[cchOutLen] = '\0'; // null terminate the encoded string
return true;
}
// thin wrapper around convert_string_from_default_encoding that handles
// allocation of the destination string. An empty string passed in returns
// failure since it's a failure case for convert_string_from_default_encoding.
wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len,
unsigned int* utf16_len )
{
*utf16_len = (mbcs_len + 1);
wchar_t* utf16_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( *utf16_len * sizeof( wchar_t )));
*utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len,
utf16_string, *utf16_len );
if( *utf16_len == 0 ) {
// we preserve the error and reset it because sqlsrv_free resets the last error
DWORD last_error = GetLastError();
sqlsrv_free( utf16_string );
SetLastError( last_error );
return NULL;
}
return utf16_string;
}
// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the
// errno is 1 based. It returns it as an array with 3 members:
// 1/SQLSTATE) sqlstate
// 2/code) driver specific error code
// 3/message) driver specific error message
// The fetch type determines if the indices are numeric, associative, or both.
bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity
TSRMLS_DC )
{
SQLHANDLE h = ctx.handle();
SQLSMALLINT h_type = ctx.handle_type();
if( h == NULL ) {
return false;
}
zval* ssphp_z = NULL;
int zr = SUCCESS;
zval* temp = NULL;
SQLRETURN r = SQL_SUCCESS;
SQLSMALLINT wmessage_len = 0;
SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ];
SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLSRV_ENCODING enc = ctx.encoding();
switch( h_type ) {
case SQL_HANDLE_STMT:
{
sqlsrv_stmt* stmt = static_cast<sqlsrv_stmt*>( &ctx );
if( stmt->current_results != NULL ) {
error = stmt->current_results->get_diag_rec( record_number );
// don't use the CHECK* macros here since it will trigger reentry into the error handling system
if( error == NULL ) {
return false;
}
break;
}
// convert the error into the encoding of the context
if( enc == SQLSRV_ENCODING_DEFAULT ) {
enc = stmt->conn->encoding();
}
}
default:
error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message,
SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len );
// don't use the CHECK* macros here since it will trigger reentry into the error handling system
if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
return false;
}
SQLINTEGER sqlstate_len = 0;
convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len);
SQLINTEGER message_len = 0;
convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len);
break;
}
// log the error first
LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate );
LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code );
LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message );
error->format = false;
return true;
}
// format and return a driver specfic error
void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error,
sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args )
{
// allocate space for the formatted message
formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
formatted_error->sqlstate = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
formatted_error->native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 ));
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast<LPSTR>( custom_error->native_message ), 0, 0,
reinterpret_cast<LPSTR>( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args );
if( rc == 0 ) {
strcpy_s( reinterpret_cast<char*>( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH,
reinterpret_cast<char*>( INTERNAL_FORMAT_ERROR ));
}
strcpy_s( reinterpret_cast<char*>( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE,
reinterpret_cast<char*>( custom_error->sqlstate ));
formatted_error->native_code = custom_error->native_code;
// log the error
LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate );
LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code );
LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message );
}
DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... )
{
va_list format_args;
va_start( format_args, format );
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args );
va_end( format_args );
return rc;
}
// return an error message for GetLastError using FormatMessage.
// this function returns the msg pointer so that it may be used within
// another function call such as handle_error
const char* get_last_error_message( DWORD last_error )
{
if( last_error == 0 ) {
last_error = GetLastError();
}
DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
last_err_msg, sizeof( last_err_msg ), NULL );
if( r == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg ));
std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg );
}
return last_err_msg;
}
// die
// Terminate the PHP request with an error message
// We use this function rather than php_error directly because we use the FormatMessage syntax in most other
// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were
// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without
// proper information. Rather than convert those messages and try and remember the difference between LOG and
// DIE, it is simpler to make the format syntax common between them.
void die( const char* msg, ... )
{
va_list format_args;
va_start( format_args, msg );
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args );
va_end( format_args );
if( rc == 0 ) {
php_error( E_ERROR, reinterpret_cast<const char*>( INTERNAL_FORMAT_ERROR ));
}
php_error( E_ERROR, last_err_msg );
}
namespace {
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string. An empty string passed in will result as
// a failure since MBTWC returns 0 for both an empty string and failure
// to convert.
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string,
unsigned int mbcs_len, __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string,
unsigned int utf16_len )
{
unsigned int win_encoding = CP_ACP;
switch( php_encoding ) {
case SQLSRV_ENCODING_CHAR:
win_encoding = CP_ACP;
break;
// this shouldn't ever be set
case SQLSRV_ENCODING_BINARY:
DIE( "Invalid encoding." );
break;
default:
win_encoding = php_encoding;
break;
}
unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len,
utf16_out_string, utf16_len );
if( required_len == 0 ) {
return 0;
}
utf16_out_string[ required_len ] = '\0';
return required_len;
}
}

View file

@ -1,668 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: init.cpp
// Contents: initialization routines for the extension
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
ZEND_GET_MODULE(g_sqlsrv)
extern "C" {
ZEND_DECLARE_MODULE_GLOBALS(sqlsrv);
}
// module global variables (initialized in minit and freed in mshutdown)
HashTable* g_ss_errors_ht = NULL;
// special list of warnings to ignore even if warnings are treated as errors
HashTable* g_ss_warnings_to_ignore_ht = NULL;
// encodings we understand
HashTable* g_ss_encodings_ht = NULL;
// henv context for creating connections
sqlsrv_context* g_henv_cp;
sqlsrv_context* g_henv_ncp;
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_INIT;
}
// argument info structures for functions, arranged alphabetically.
// see zend_API.h in the PHP sources for more information about these macros
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, setting )
ZEND_ARG_INFO( 0, value )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, server_name )
ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 )
ZEND_ARG_INFO( 0, errors_and_or_warnings )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 1, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, fetch_type )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, class_name )
ZEND_ARG_INFO( 0, ctor_params )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, setting )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 1, 2 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, field_index )
ZEND_ARG_INFO( 0, get_as_type )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 )
ZEND_ARG_INFO( 0, conn )
ZEND_ARG_INFO( 0, tsql )
ZEND_ARG_INFO( 0, params )
ZEND_ARG_INFO( 0, options )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 )
ZEND_ARG_INFO( 0, conn )
ZEND_ARG_INFO( 0, tsql )
ZEND_ARG_INFO( 0, params )
ZEND_ARG_INFO( 0, options )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 )
ZEND_ARG_INFO( 0, size )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 )
ZEND_ARG_INFO( 0, precision )
ZEND_ARG_INFO( 0, scale )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 )
ZEND_ARG_INFO( 0, encoding )
ZEND_END_ARG_INFO()
// function table with associated arginfo structures
zend_function_entry sqlsrv_functions[] = {
PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo )
PHP_FE( sqlsrv_close, sqlsrv_close_arginfo )
PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo )
PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo )
PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo )
PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo )
PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo )
PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo )
PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo )
PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo )
PHP_FE( sqlsrv_query, sqlsrv_query_arginfo )
PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo )
PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo )
PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo )
PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo )
PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo )
PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo )
PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo )
PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo )
PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo )
PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo )
PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo )
PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo )
PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo )
PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo )
PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo )
PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo )
PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo )
PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo )
{NULL, NULL, NULL} // end of the table
};
// the structure returned to Zend that exposes the extension to the Zend engine.
// this structure is defined in zend_modules.h in the PHP sources
zend_module_entry g_sqlsrv_module_entry =
{
STANDARD_MODULE_HEADER,
"sqlsrv",
sqlsrv_functions, // exported function table
// initialization and shutdown functions
PHP_MINIT(sqlsrv),
PHP_MSHUTDOWN(sqlsrv),
PHP_RINIT(sqlsrv),
PHP_RSHUTDOWN(sqlsrv),
PHP_MINFO(sqlsrv),
// version of the extension. Matches the version resource of the extension dll
VER_FILEVERSION_STR,
PHP_MODULE_GLOBALS(sqlsrv),
NULL,
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
// Module initialization
// This function is called once per execution of the Zend engine
// We use it to:
// 1) Register our constants. See MSDN or the function below for the exact constants
// we register.
// 2) Register our resource types (connection, statement, and stream types)
// 3) Allocate the environment handles for ODBC connections (1 for non pooled
// connections and 1 for pooled connections)
// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries
PHP_MINIT_FUNCTION(sqlsrv)
{
SQLSRV_UNUSED( type );
core_sqlsrv_register_logger( ss_sqlsrv_log );
// our global variables are initialized in the RINIT function
#if defined(ZTS)
if( ts_allocate_id( &sqlsrv_globals_id,
sizeof( zend_sqlsrv_globals ),
(ts_allocate_ctor) NULL,
(ts_allocate_dtor) NULL ) == 0 )
return FAILURE;
#endif
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long ));
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long ));
REGISTER_INI_ENTRIES();
LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set
// register connection resource
ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection",
module_number );
if( ss_sqlsrv_conn::descriptor == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ );
return FAILURE;
}
// register statement resources
ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number );
if( ss_sqlsrv_stmt::descriptor == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ );
return FAILURE;
}
sqlsrv_sqltype constant_type;
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", "binary", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", "char", CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 23;
constant_type.typeinfo.scale = 3;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_DECIMAL;
constant_type.typeinfo.size = 19;
constant_type.typeinfo.scale = 4;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_DECIMAL;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 4;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_BINARY;
constant_type.typeinfo.size = 8;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_DATE;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIME2;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET;
constant_type.typeinfo.size = 34;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 27;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", "forward", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", "static", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", "dynamic", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", "keyset", CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", "buffered", CONST_PERSISTENT | CONST_CS );
try {
// initialize list of warnings to ignore
g_ss_warnings_to_ignore_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
int zr = ::zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, NULL, 1 );
if( zr == FAILURE ) {
throw ss::SSException();
}
sqlsrv_error_const error_to_ignore;
// changed database warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 5701;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL );
if( zr == FAILURE ) {
throw ss::SSException();
}
// changed language warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 5703;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL );
if( zr == FAILURE ) {
throw ss::SSException();
}
// option value changed
error_to_ignore.sqlstate = (SQLCHAR*)"01S02";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL );
if( zr == FAILURE ) {
throw ss::SSException();
}
// cursor operation conflict
error_to_ignore.sqlstate = (SQLCHAR*)"01001";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL );
if( zr == FAILURE ) {
throw ss::SSException();
}
// null value eliminated in set function
error_to_ignore.sqlstate = (SQLCHAR*)"01003";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL );
if( zr == FAILURE ) {
throw ss::SSException();
}
// SQL Azure warning: This session has been assigned a tracing id of ..
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 40608;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL );
if( zr == FAILURE ) {
throw ss::SSException();
}
// Full-text search condition contained noise words warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 9927;
error_to_ignore.format = false;
zr = zend_hash_next_index_insert(g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof(sqlsrv_error_const), NULL);
if (zr == FAILURE) {
throw ss::SSException();
}
}
catch( ss::SSException& ) {
LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" );
return FAILURE;
}
try {
// supported encodings
g_ss_encodings_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
int zr = zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, NULL /*no resource destructor*/, 1 /*persistent*/ );
if( zr == FAILURE ) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR );
zr = zend_hash_next_index_insert( g_ss_encodings_ht, &sql_enc_char, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true );
zr = zend_hash_next_index_insert( g_ss_encodings_ht, &sql_enc_bin, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 );
zr = zend_hash_next_index_insert( g_ss_encodings_ht, &sql_enc_utf8, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ );
if( zr == FAILURE ) {
throw ss::SSException();
}
}
catch( ss::SSException& ) {
LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
// initialize list of sqlsrv errors
g_ss_errors_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
int zr = ::zend_hash_init( g_ss_errors_ht, 50, NULL, NULL, 1 );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: Failed to initialize the sqlsrv errors hashtable.", _FN_ );
return FAILURE;
}
for( int i = 0; SS_ERRORS[ i ].error_code != -1; ++i ) {
zr = ::zend_hash_index_update( g_ss_errors_ht, SS_ERRORS[ i ].error_code,
&( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ), NULL );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ );
return FAILURE;
}
}
if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ );
return FAILURE;
}
try {
// retrieve the handles for the environments
core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC );
}
catch( core::CoreException& ) {
return FAILURE;
}
catch( ... ) {
LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." );
return FAILURE;
}
return SUCCESS;
}
// Module shutdown function
// Free the environment handles allocated in MINIT and unregister our stream wrapper.
// Resource types and constants are automatically released since we don't flag them as
// persistent when they are registered.
PHP_MSHUTDOWN_FUNCTION(sqlsrv)
{
SQLSRV_UNUSED( type );
UNREGISTER_INI_ENTRIES();
// clean up the list of sqlsrv errors
zend_hash_destroy( g_ss_errors_ht );
pefree( g_ss_errors_ht, 1 /*persistent*/ );
zend_hash_destroy( g_ss_warnings_to_ignore_ht );
pefree( g_ss_warnings_to_ignore_ht, 1 );
zend_hash_destroy( g_ss_encodings_ht );
pefree( g_ss_encodings_ht, 1 );
core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp );
if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER TSRMLS_CC ) == FAILURE ) {
return FAILURE;
}
return SUCCESS;
}
// Request initialization function
// This function is called once per PHP script execution
// Initialize request globals used in the request, including those that correspond to INI entries.
// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not
// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored
// (such as return values from stored procedures), add them to this collection and they won't be
// returned as errors. Or you could just set WarningsReturnAsErrors to false.
PHP_RINIT_FUNCTION(sqlsrv)
{
SQLSRV_UNUSED( module_number );
SQLSRV_UNUSED( type );
SQLSRV_G( warnings_return_as_errors ) = true;
ALLOC_INIT_ZVAL( SQLSRV_G( errors ));
Z_SET_ISREF_P( SQLSRV_G( errors ));
ALLOC_INIT_ZVAL( SQLSRV_G( warnings ));
Z_SET_ISREF_P( SQLSRV_G( warnings ));
LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" );
// read INI settings
SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS );
SQLSRV_G( log_severity ) = INI_INT( INI_PREFIX INI_LOG_SEVERITY );
SQLSRV_G( log_subsystems ) = INI_INT( INI_PREFIX INI_LOG_SUBSYSTEMS );
SQLSRV_G( buffered_query_limit ) = INI_INT( INI_PREFIX INI_BUFFERED_QUERY_LIMIT );
LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off");
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity ));
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems ));
LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit ));
// verify memory at the end of the request (in debug mode only)
full_mem_check(MEMCHECK_SILENT);
return SUCCESS;
}
// Request shutdown
// Called at the end of a script's execution
// Simply releases the variables allocated during request initialization.
PHP_RSHUTDOWN_FUNCTION(sqlsrv)
{
SQLSRV_UNUSED( module_number );
SQLSRV_UNUSED( type );
LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" );
reset_errors( TSRMLS_C );
zval_ptr_dtor( &SQLSRV_G( errors ));
zval_ptr_dtor( &SQLSRV_G( warnings ));
// verify memory at the end of the request (in debug mode only)
full_mem_check(MEMCHECK_SILENT);
return SUCCESS;
}
// Called for php_info(); Displays the INI settings registered and their current values
PHP_MINFO_FUNCTION(sqlsrv)
{
#if defined(ZTS)
SQLSRV_UNUSED( tsrm_ls );
#endif
php_info_print_table_start();
php_info_print_table_header(2, "sqlsrv support", "enabled");
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}

View file

@ -1,2343 +0,0 @@
//-----------------------------------------------------------------------------
// File: msodbcsql.h
//
// Copyright: Copyright (c) Microsoft Corporation
//
// Contents: ODBC driver for SQL Server specific definitions.
//
//-----------------------------------------------------------------------------
#ifndef __msodbcsql_h__
#define __msodbcsql_h__
#if !defined(SQLODBC_VER)
#define SQLODBC_VER 1100
#endif
#if SQLODBC_VER >= 1100
#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server"
#define SQLODBC_FILE_NAME_ANSI "msodbcsql"
#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11"
#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll"
#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server"
#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server"
#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql"
#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11"
#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll"
// define the character type agnostic constants
#if defined(_UNICODE) || defined(UNICODE)
#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE
#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE
#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE
#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE
#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE
#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE
#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE
#else // _UNICODE || UNICODE
#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI
#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI
#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI
#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI
#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI
#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI
#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI
#endif // _UNICODE || UNICODE
#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER
#endif // SQLODBC_VER
#ifndef __sqlncli_h__
#if !defined(SQLNCLI_VER)
#define SQLNCLI_VER 1100
#endif
#if SQLNCLI_VER >= 1100
#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client"
#define SQLNCLI_FILE_NAME_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11"
#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll"
#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client"
#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11"
#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll"
#elif SQLNCLI_VER >= 1000
#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client"
#define SQLNCLI_FILE_NAME_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10"
#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll"
#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0"
#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client"
#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10"
#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll"
#else
#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client"
#define SQLNCLI_FILE_NAME_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli"
#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll"
#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client"
#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client"
#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli"
#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll"
#endif // SQLNCLI_VER >= 1100
// define the character type agnostic constants
#if defined(_UNICODE) || defined(UNICODE)
#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE
#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE
#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE
#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE
#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE
#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE
#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE
#else // _UNICODE || UNICODE
#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI
#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI
#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI
#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI
#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI
#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI
#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI
#endif // _UNICODE || UNICODE
#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER
#ifdef ODBCVER
#ifdef __cplusplus
extern "C" {
#endif
// max SQL Server identifier length
#define SQL_MAX_SQLSERVERNAME 128
// SQLSetConnectAttr driver specific defines.
// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage.
// Connection attributes
#define SQL_COPT_SS_BASE 1200
#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only
#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare
#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login
#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact
#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata
#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction
#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction
#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections
#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data
#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data
#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds.
#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies.
#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging.
#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk.
#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers
#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings
#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection
#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation
#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database
#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL
#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data
#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection
#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server
#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login
#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level
#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate
#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN
#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN
#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection
#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated
#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID
// Define old names
#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD
#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP
#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY
#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS
// SQLSetStmtAttr SQL Server Native Client driver specific defines.
// Statement attributes
#define SQL_SOPT_SS_BASE 1225
#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging
#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only
#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns
#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option
#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions
#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options
#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator
#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary
#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout
#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text
#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name
#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types
#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls
#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE
// Define old names
#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING
#define SQL_COPT_SS_BASE_EX 1240
#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation
#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request.
#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion.
#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check
#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K)
#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet
#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent
#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover
#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER
// SQLColAttributes driver specific defines.
// SQLSetDescField/SQLGetDescField driver specific defines.
// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_CA_SS_BASE 1200
#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype
#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype
#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders
#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol
#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen
#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute
#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return
#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist
#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid
#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop
#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen
#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE)
#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE)
//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location.
#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars)
#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15)
#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16)
#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17)
// XML, CLR UDT, and table valued parameter related metadata
#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name
#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name
#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name
#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class
#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection
#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection
#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection
#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name
#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name
#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name
// table valued parameter related metadata
#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed
#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key
#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order
#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal
#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter
// sparse column related metadata
#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns
// Legacy datetime related metadata
#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types
#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36)
// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE
#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option
// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP
#define SQL_UP_OFF 0L // Procedures won't be used for prepare
#define SQL_UP_ON 1L // Procedures will be used for prepare
#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped
#define SQL_UP_DEFAULT SQL_UP_ON
// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only
#define SQL_IS_OFF 0L // Integrated security isn't used
#define SQL_IS_ON 1L // Integrated security is used
#define SQL_IS_DEFAULT SQL_IS_OFF
// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS
#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact
#define SQL_PC_ON 1L // Cursors remain open on SQLTransact
#define SQL_PC_DEFAULT SQL_PC_OFF
// Defines for use with SQL_COPT_SS_USER_DATA
#define SQL_UD_NOTSET NULL // No user data pointer set
// Defines for use with SQL_COPT_SS_TRANSLATE
#define SQL_XL_OFF 0L // Code page translation is not performed
#define SQL_XL_ON 1L // Code page translation is performed
#define SQL_XL_DEFAULT SQL_XL_ON
// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only
#define SQL_FB_OFF 0L // FallBack connections are disabled
#define SQL_FB_ON 1L // FallBack connections are enabled
#define SQL_FB_DEFAULT SQL_FB_OFF
// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only
#define SQL_BCP_OFF 0L // BCP is not allowed on connection
#define SQL_BCP_ON 1L // BCP is allowed on connection
#define SQL_BCP_DEFAULT SQL_BCP_OFF
// Defines for use with SQL_COPT_SS_QUOTED_IDENT
#define SQL_QI_OFF 0L // Quoted identifiers are enable
#define SQL_QI_ON 1L // Quoted identifiers are disabled
#define SQL_QI_DEFAULT SQL_QI_ON
// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only
#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled
#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled
#define SQL_AD_DEFAULT SQL_AD_ON
// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only
#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off
#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on
#define SQL_CN_DEFAULT SQL_CN_ON
// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING
#define SQL_TL_OFF 0L // No logging on text pointer ops
#define SQL_TL_ON 1L // Logging occurs on text pointer ops
#define SQL_TL_DEFAULT SQL_TL_ON
// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS
#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden
#define SQL_HC_ON 1L // FOR BROWSE columns are exposed
#define SQL_HC_DEFAULT SQL_HC_OFF
// Defines for use with SQL_SOPT_SS_NOBROWSETABLE
#define SQL_NB_OFF 0L // NO_BROWSETABLE is off
#define SQL_NB_ON 1L // NO_BROWSETABLE is on
#define SQL_NB_DEFAULT SQL_NB_OFF
// Defines for use with SQL_SOPT_SS_REGIONALIZE
#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions
#define SQL_RE_ON 1L // Regionalization occurs on output character conversions
#define SQL_RE_DEFAULT SQL_RE_OFF
// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS
#define SQL_CO_OFF 0L // Clear all cursor options
#define SQL_CO_FFO 1L // Fast-forward cursor will be used
#define SQL_CO_AF 2L // Autofetch on cursor open
#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch
#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors
#define SQL_CO_DEFAULT SQL_CO_OFF
//SQL_SOPT_SS_NOCOUNT_STATUS
#define SQL_NC_OFF 0L
#define SQL_NC_ON 1L
//SQL_SOPT_SS_DEFER_PREPARE
#define SQL_DP_OFF 0L
#define SQL_DP_ON 1L
//SQL_SOPT_SS_NAME_SCOPE
#define SQL_SS_NAME_SCOPE_TABLE 0L
#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L
#define SQL_SS_NAME_SCOPE_EXTENDED 2L
#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L
#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE
//SQL_COPT_SS_ENCRYPT
#define SQL_EN_OFF 0L
#define SQL_EN_ON 1L
//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE
#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L
#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L
//SQL_COPT_SS_BROWSE_CONNECT
#define SQL_MORE_INFO_NO 0L
#define SQL_MORE_INFO_YES 1L
//SQL_COPT_SS_BROWSE_CACHE_DATA
#define SQL_CACHE_DATA_NO 0L
#define SQL_CACHE_DATA_YES 1L
//SQL_COPT_SS_RESET_CONNECTION
#define SQL_RESET_YES 1L
//SQL_COPT_SS_WARN_ON_CP_ERROR
#define SQL_WARN_NO 0L
#define SQL_WARN_YES 1L
//SQL_COPT_SS_MARS_ENABLED
#define SQL_MARS_ENABLED_NO 0L
#define SQL_MARS_ENABLED_YES 1L
/* SQL_TXN_ISOLATION_OPTION bitmasks */
#define SQL_TXN_SS_SNAPSHOT 0x00000020L
// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER
#define SQL_SS_ORDER_UNSPECIFIED 0L
#define SQL_SS_DESCENDING_ORDER 1L
#define SQL_SS_ASCENDING_ORDER 2L
#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED
// Driver specific SQL data type defines.
// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_SS_VARIANT (-150)
#define SQL_SS_UDT (-151)
#define SQL_SS_XML (-152)
#define SQL_SS_TABLE (-153)
#define SQL_SS_TIME2 (-154)
#define SQL_SS_TIMESTAMPOFFSET (-155)
// Local types to be used with SQL_CA_SS_SERVER_TYPE
#define SQL_SS_TYPE_DEFAULT 0L
#define SQL_SS_TYPE_SMALLDATETIME 1L
#define SQL_SS_TYPE_DATETIME 2L
// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager.
#define SQL_C_TYPES_EXTENDED 0x04000L
#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0)
#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1)
#ifndef SQLNCLI_NO_BCP
// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application
// and you want to exclude the BCP-related definitions in this header file.
// SQL Server Data Type defines.
// New types for SQL 6.0 and later servers
#define SQLTEXT 0x23
#define SQLVARBINARY 0x25
#define SQLINTN 0x26
#define SQLVARCHAR 0x27
#define SQLBINARY 0x2d
#define SQLIMAGE 0x22
#define SQLCHARACTER 0x2f
#define SQLINT1 0x30
#define SQLBIT 0x32
#define SQLINT2 0x34
#define SQLINT4 0x38
#define SQLMONEY 0x3c
#define SQLDATETIME 0x3d
#define SQLFLT8 0x3e
#define SQLFLTN 0x6d
#define SQLMONEYN 0x6e
#define SQLDATETIMN 0x6f
#define SQLFLT4 0x3b
#define SQLMONEY4 0x7a
#define SQLDATETIM4 0x3a
// New types for SQL 6.0 and later servers
#define SQLDECIMAL 0x6a
#define SQLNUMERIC 0x6c
// New types for SQL 7.0 and later servers
#define SQLUNIQUEID 0x24
#define SQLBIGCHAR 0xaf
#define SQLBIGVARCHAR 0xa7
#define SQLBIGBINARY 0xad
#define SQLBIGVARBINARY 0xa5
#define SQLBITN 0x68
#define SQLNCHAR 0xef
#define SQLNVARCHAR 0xe7
#define SQLNTEXT 0x63
// New types for SQL 2000 and later servers
#define SQLINT8 0x7f
#define SQLVARIANT 0x62
// New types for SQL 2005 and later servers
#define SQLUDT 0xf0
#define SQLXML 0xf1
// New types for SQL 2008 and later servers
#define SQLTABLE 0xf3
#define SQLDATEN 0x28
#define SQLTIMEN 0x29
#define SQLDATETIME2N 0x2a
#define SQLDATETIMEOFFSETN 0x2b
// Define old names
#define SQLDECIMALN 0x6a
#define SQLNUMERICN 0x6c
#endif // SQLNCLI_NO_BCP
// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of
// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns
#define SQL_SS_LENGTH_UNLIMITED 0
// User Data Type definitions.
// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE.
#define SQLudtBINARY 3
#define SQLudtBIT 16
#define SQLudtBITN 0
#define SQLudtCHAR 1
#define SQLudtDATETIM4 22
#define SQLudtDATETIME 12
#define SQLudtDATETIMN 15
#define SQLudtDECML 24
#define SQLudtDECMLN 26
#define SQLudtFLT4 23
#define SQLudtFLT8 8
#define SQLudtFLTN 14
#define SQLudtIMAGE 20
#define SQLudtINT1 5
#define SQLudtINT2 6
#define SQLudtINT4 7
#define SQLudtINTN 13
#define SQLudtMONEY 11
#define SQLudtMONEY4 21
#define SQLudtMONEYN 17
#define SQLudtNUM 10
#define SQLudtNUMN 25
#define SQLudtSYSNAME 18
#define SQLudtTEXT 19
#define SQLudtTIMESTAMP 80
#define SQLudtUNIQUEIDENTIFIER 0
#define SQLudtVARBINARY 4
#define SQLudtVARCHAR 2
#define MIN_USER_DATATYPE 256
// Aggregate operator types.
// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP.
#define SQLAOPSTDEV 0x30 // Standard deviation
#define SQLAOPSTDEVP 0x31 // Standard deviation population
#define SQLAOPVAR 0x32 // Variance
#define SQLAOPVARP 0x33 // Variance population
#define SQLAOPCNT 0x4b // Count
#define SQLAOPSUM 0x4d // Sum
#define SQLAOPAVG 0x4f // Average
#define SQLAOPMIN 0x51 // Min
#define SQLAOPMAX 0x52 // Max
#define SQLAOPANY 0x53 // Any
#define SQLAOPNOOP 0x56 // None
// SQLGetInfo driver specific defines.
// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_INFO_SS_FIRST 1199
#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo
#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo
#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA
#ifdef UNICODE
#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW
#else
#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA
#endif
// SQLGetDiagField driver specific defines.
// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_DIAG_SS_BASE (-1150)
#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE)
#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1)
#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2)
#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3)
#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4)
// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines.
// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage.
#define SQL_DIAG_DFC_SS_BASE (-200)
#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0)
#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1)
#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2)
#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3)
#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4)
#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5)
#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6)
#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7)
#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8)
#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9)
#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10)
#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11)
#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12)
#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13)
#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14)
#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15)
#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16)
#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17)
#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18)
#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19)
#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20)
#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20)
#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21)
#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22)
#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22)
#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23)
#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24)
#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25)
#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26)
#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26)
#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27)
#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27)
#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28)
#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29)
#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29)
#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30)
#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31)
#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32)
#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33)
#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34)
#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35)
#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36)
#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37)
#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38)
#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39)
#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40)
#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41)
#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42)
#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43)
#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44)
#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45)
#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46)
#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47)
#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48)
#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49)
#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50)
#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51)
#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52)
#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53)
#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54)
#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55)
#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56)
// Severity codes for SQL_DIAG_SS_SEVERITY
#define EX_ANY 0
#define EX_INFO 10
#define EX_MAXISEVERITY EX_INFO
#define EX_MISSING 11
#define EX_TYPE 12
#define EX_DEADLOCK 13
#define EX_PERMIT 14
#define EX_SYNTAX 15
#define EX_USER 16
#define EX_RESOURCE 17
#define EX_INTOK 18
#define MAXUSEVERITY EX_INTOK
#define EX_LIMIT 19
#define EX_CMDFATAL 20
#define MINFATALERR EX_CMDFATAL
#define EX_DBFATAL 21
#define EX_TABCORRUPT 22
#define EX_DBCORRUPT 23
#define EX_HARDWARE 24
#define EX_CONTROL 25
// Internal server datatypes - used when binding to SQL_C_BINARY
#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts
// DB-Library datatypes
#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte
#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte
#ifdef UNICODE
typedef wchar_t DBCHAR;
#else
typedef char DBCHAR;
#endif
typedef short SQLSMALLINT;
typedef unsigned short SQLUSMALLINT;
typedef unsigned char DBBINARY;
typedef unsigned char DBTINYINT;
typedef short DBSMALLINT;
typedef unsigned short DBUSMALLINT;
typedef double DBFLT8;
typedef unsigned char DBBIT;
typedef unsigned char DBBOOL;
typedef float DBFLT4;
typedef DBFLT4 DBREAL;
typedef UINT DBUBOOL;
typedef struct dbmoney
{
LONG mnyhigh;
ULONG mnylow;
} DBMONEY;
typedef struct dbdatetime
{
LONG dtdays;
ULONG dttime;
} DBDATETIME;
typedef struct dbdatetime4
{
USHORT numdays;
USHORT nummins;
} DBDATETIM4;
typedef LONG DBMONEY4;
#include <pshpack8.h> // 8-byte structure packing
// New Date Time Structures
// New Structure for TIME2
typedef struct tagSS_TIME2_STRUCT
{
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
} SQL_SS_TIME2_STRUCT;
// New Structure for TIMESTAMPOFFSET
typedef struct tagSS_TIMESTAMPOFFSET_STRUCT
{
SQLSMALLINT year;
SQLUSMALLINT month;
SQLUSMALLINT day;
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
SQLSMALLINT timezone_hour;
SQLSMALLINT timezone_minute;
} SQL_SS_TIMESTAMPOFFSET_STRUCT;
typedef struct tagDBTIME2
{
USHORT hour;
USHORT minute;
USHORT second;
ULONG fraction;
} DBTIME2;
typedef struct tagDBTIMESTAMPOFFSET
{
SHORT year;
USHORT month;
USHORT day;
USHORT hour;
USHORT minute;
USHORT second;
ULONG fraction;
SHORT timezone_hour;
SHORT timezone_minute;
} DBTIMESTAMPOFFSET;
#include <poppack.h> // restore original structure packing
// Money value *10,000
#define DBNUM_PREC_TYPE BYTE
#define DBNUM_SCALE_TYPE BYTE
#define DBNUM_VAL_TYPE BYTE
#if (ODBCVER < 0x0300)
#define MAXNUMERICLEN 16
typedef struct dbnumeric // Internal representation of NUMERIC data type
{
DBNUM_PREC_TYPE precision; // Precision
DBNUM_SCALE_TYPE scale; // Scale
BYTE sign; // Sign (1 if positive, 0 if negative)
DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value
} DBNUMERIC;
typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type
#else // Use ODBC 3.0 definitions since same as DBLib
#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN
typedef SQL_NUMERIC_STRUCT DBNUMERIC;
typedef SQL_NUMERIC_STRUCT DBDECIMAL;
#endif // ODCBVER
#endif // MAXNUMERICLEN
#ifndef INT
typedef int INT;
typedef LONG DBINT;
typedef DBINT * LPDBINT;
#ifndef _LPCBYTE_DEFINED
#define _LPCBYTE_DEFINED
typedef BYTE const* LPCBYTE;
#endif //_LPCBYTE_DEFINED
#endif // INT
/**************************************************************************
This struct is a global used for gathering statistical data on the driver.
Access to this structure is controlled via the pStatCrit;
***************************************************************************/
typedef struct sqlperf
{
// Application Profile Statistics
DWORD TimerResolution;
DWORD SQLidu;
DWORD SQLiduRows;
DWORD SQLSelects;
DWORD SQLSelectRows;
DWORD Transactions;
DWORD SQLPrepares;
DWORD ExecDirects;
DWORD SQLExecutes;
DWORD CursorOpens;
DWORD CursorSize;
DWORD CursorUsed;
LDOUBLE PercentCursorUsed;
LDOUBLE AvgFetchTime;
LDOUBLE AvgCursorSize;
LDOUBLE AvgCursorUsed;
DWORD SQLFetchTime;
DWORD SQLFetchCount;
DWORD CurrentStmtCount;
DWORD MaxOpenStmt;
DWORD SumOpenStmt;
// Connection Statistics
DWORD CurrentConnectionCount;
DWORD MaxConnectionsOpened;
DWORD SumConnectionsOpened;
DWORD SumConnectiontime;
LDOUBLE AvgTimeOpened;
// Network Statistics
DWORD ServerRndTrips;
DWORD BuffersSent;
DWORD BuffersRec;
DWORD BytesSent;
DWORD BytesRec;
// Time Statistics;
DWORD msExecutionTime;
DWORD msNetWorkServerTime;
} SQLPERF;
// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY
#define SQL_PERF_START 1 // Starts the driver sampling performance data.
#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data.
// The following are defines for SQL_COPT_SS_PERF_DATA_LOG
#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG")
// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG
#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG")
// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL
#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds
#ifndef SQLNCLI_NO_BCP
// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application
// and you want to exclude the BCP-related definitions in this header file.
// ODBC BCP prototypes and defines
// Return codes
#define SUCCEED 1
#define FAIL 0
#define SUCCEED_ABORT 2
#define SUCCEED_ASYNC 3
// Transfer directions
#define DB_IN 1 // Transfer from client to server
#define DB_OUT 2 // Transfer from server to client
// bcp_control option
#define BCPMAXERRS 1 // Sets max errors allowed
#define BCPFIRST 2 // Sets first row to be copied out
#define BCPLAST 3 // Sets number of rows to be copied out
#define BCPBATCH 4 // Sets input batch size
#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values
#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT
#define BCPODBC 7 // Sets ODBC canonical character output
#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on
#if SQLNCLI_VER < 1000
#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on
#endif
#define BCPHINTSA 10 // Sets server BCP hints (ANSI string)
#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string)
#define BCPFILECP 12 // Sets clients code page for the file
#define BCPUNICODEFILE 13 // Sets that the file contains unicode header
#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically
#define BCPFILEFMT 15 // Sets file format version
#define BCPFMTXML 16 // Sets the format file type to xml
#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit)
#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit)
#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit)
#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec
// BCPFILECP values
// Any valid code page that is installed on the client can be passed plus:
#define BCPFILECP_ACP 0 // Data in file is in Windows code page
#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default)
#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion)
// bcp_collen definition
#define SQL_VARLEN_DATA (-10) // Use default length for column
// BCP column format properties
#define BCP_FMT_TYPE 0x01
#define BCP_FMT_INDICATOR_LEN 0x02
#define BCP_FMT_DATA_LEN 0x03
#define BCP_FMT_TERMINATOR 0x04
#define BCP_FMT_SERVER_COL 0x05
#define BCP_FMT_COLLATION 0x06
#define BCP_FMT_COLLATION_ID 0x07
// bcp_setbulkmode properties
#define BCP_OUT_CHARACTER_MODE 0x01
#define BCP_OUT_WIDE_CHARACTER_MODE 0x02
#define BCP_OUT_NATIVE_TEXT_MODE 0x03
#define BCP_OUT_NATIVE_MODE 0x04
// BCP functions
DBINT SQL_API bcp_batch (HDBC);
RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT);
RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT);
RETCODE SQL_API bcp_collen (HDBC, DBINT, INT);
RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT);
RETCODE SQL_API bcp_columns (HDBC, INT);
RETCODE SQL_API bcp_control (HDBC, INT, void *);
DBINT SQL_API bcp_done (HDBC);
RETCODE SQL_API bcp_exec (HDBC, LPDBINT);
RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *);
RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT);
RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT);
RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE);
RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR);
RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR);
RETCODE SQL_API bcp_sendrow (HDBC);
RETCODE SQL_API bcp_setbulkmode (HDBC, INT, __in_bcount(cbField) void*, INT cbField, __in_bcount(cbRow) void *, INT cbRow);
RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT);
RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR);
RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR);
CHAR* SQL_API dbprtypeA (INT);
WCHAR* SQL_API dbprtypeW (INT);
CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL);
WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL);
#ifdef UNICODE
#define bcp_init bcp_initW
#define bcp_readfmt bcp_readfmtW
#define bcp_writefmt bcp_writefmtW
#define dbprtype dbprtypeW
#define bcp_gettypename bcp_gettypenameW
#define BCPHINTS BCPHINTSW
#else
#define bcp_init bcp_initA
#define bcp_readfmt bcp_readfmtA
#define bcp_writefmt bcp_writefmtA
#define dbprtype dbprtypeA
#define bcp_gettypename bcp_gettypenameA
#define BCPHINTS BCPHINTSA
#endif // UNICODE
#endif // SQLNCLI_NO_BCP
// The following options have been deprecated
#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0)
// Defines for use with SQL_FAST_CONNECT - only useable before connecting
#define SQL_FC_OFF 0L // Fast connect is off
#define SQL_FC_ON 1L // Fast connect is on
#define SQL_FC_DEFAULT SQL_FC_OFF
#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6)
#define SQL_AO_OFF 0L
#define SQL_AO_ON 1L
#define SQL_AO_DEFAULT SQL_AO_OFF
#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME
#ifdef __cplusplus
} // extern "C"
#endif
#endif // ODBCVER
#ifdef __cplusplus
extern "C" {
#endif
#include <windows.h>
//The following facilitates opening a handle to a SQL filestream
typedef enum _SQL_FILESTREAM_DESIRED_ACCESS {
SQL_FILESTREAM_READ = 0,
SQL_FILESTREAM_WRITE = 1,
SQL_FILESTREAM_READWRITE = 2
} SQL_FILESTREAM_DESIRED_ACCESS;
#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L
#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L
#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L
#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L
#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L
HANDLE __stdcall OpenSqlFilestream (
LPCWSTR FilestreamPath,
SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess,
ULONG OpenOptions,
__in_bcount(FilestreamTransactionContextLength)
LPBYTE FilestreamTransactionContext,
SSIZE_T FilestreamTransactionContextLength,
PLARGE_INTEGER AllocationSize);
#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS)
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__sqlncli_h__
#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount
#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval
#ifdef SQL_COPT_SS_MAX_USED
#undef SQL_COPT_SS_MAX_USED
#endif // SQL_COPT_SS_MAX_USED
#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL
#ifndef _SQLUSERINSTANCE_H_
#define _SQLUSERINSTANCE_H_
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
// Recommended buffer size to store a LocalDB connection string
#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260
// type definition for LocalDBCreateInstance function
typedef HRESULT __cdecl FnLocalDBCreateInstance (
// I the LocalDB version (e.g. 11.0 or 11.0.1094.2)
__in_z PCWSTR wszVersion,
// I the instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
);
// type definition for pointer to LocalDBCreateInstance function
typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance;
// type definition for LocalDBStartInstance function
typedef HRESULT __cdecl FnLocalDBStartInstance (
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags,
// O the buffer to store the connection string to the LocalDB instance
__out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection,
// I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null.
__inout_opt LPDWORD lpcchSqlConnection
);
// type definition for pointer to LocalDBStartInstance function
typedef FnLocalDBStartInstance* PFnLocalDBStartInstance;
// Flags for the LocalDBFormatMessage function
#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L
// type definition for LocalDBFormatMessage function
typedef HRESULT __cdecl FnLocalDBFormatMessage(
// I the LocalDB error code
__in HRESULT hrLocalDB,
// I Available flags:
// LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short,
// the error message will be truncated to fit into the buffer
__in DWORD dwFlags,
// I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used)
__in DWORD dwLanguageId,
// O the buffer to store the LocalDB error message
__out_ecount_z(*lpcchMessage) LPWSTR wszMessage,
// I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null. If the function succeeds
// contains the number of characters in the message, excluding the trailing null
__inout LPDWORD lpcchMessage
);
// type definition for function pointer to LocalDBFormatMessage function
typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage;
// MessageId: LOCALDB_ERROR_NOT_INSTALLED
//
// MessageText:
//
// LocalDB is not installed.
//
#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L)
//---------------------------------------------------------------------
// Function: LocalDBCreateInstance
//
// Description: This function will create the new LocalDB instance.
//
// Available Flags:
// No flags available. Reserved for future use.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance
// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH
// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed
// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty
// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version
// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile%
// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified
// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created
// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed.
// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted
//
FnLocalDBCreateInstance LocalDBCreateInstance;
//---------------------------------------------------------------------
// Function: LocalDBStartInstance
//
// Description: This function will start the given LocalDB instance.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small
// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH
// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed
// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified
// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created
// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed.
// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted
//
FnLocalDBStartInstance LocalDBStartInstance;
// type definition for LocalDBStopInstance function
typedef HRESULT __cdecl FnLocalDBStopInstance (
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I Available flags:
// LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately
// LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option
__in DWORD dwFlags,
// I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately
// without waiting for LocalDB instance to stop
__in ULONG ulTimeout
);
// type definition for pointer to LocalDBStopInstance function
typedef FnLocalDBStopInstance* PFnLocalDBStopInstance;
// Flags for the StopLocalDBInstance function
#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L
#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L
//---------------------------------------------------------------------
// Function: LocalDBStopInstance
//
// Description: This function will shutdown the given LocalDB instance.
// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately.
// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBStopInstance LocalDBStopInstance;
// type definition for LocalDBDeleteInstance function
typedef HRESULT __cdecl FnLocalDBDeleteInstance (
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
);
// type definition for pointer to LocalDBDeleteInstance function
typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance;
//---------------------------------------------------------------------
// Function: LocalDBDeleteInstance
//
// Description: This function will remove the given LocalDB instance. If the given instance is running this function will
// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY.
//
// Return Values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist
// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBDeleteInstance LocalDBDeleteInstance;
// Function: LocalDBFormatMessage
//
// Description: This function will return the localized textual description for the given LocalDB error
//
// Available Flags:
// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer
//
// Return Value:
// S_OK, if the function succeeds
//
// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown
// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language)
// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown
// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag
// is not set
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBFormatMessage LocalDBFormatMessage;
#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128
#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME
typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1];
typedef TLocalDBInstanceName* PTLocalDBInstanceName;
// type definition for LocalDBGetInstances function
typedef HRESULT __cdecl FnLocalDBGetInstances(
// O buffer for a LocalDB instance names
__out PTLocalDBInstanceName pInstanceNames,
// I/O on input has the number slots for instance names in the pInstanceNames buffer. On output,
// has the number of existing LocalDB instances
__inout LPDWORD lpdwNumberOfInstances
);
// type definition for pointer to LocalDBGetInstances function
typedef FnLocalDBGetInstances* PFnLocalDBGetInstances;
// Function: LocalDBGetInstances
//
// Description: This function returns names for all existing Local DB instances
//
// Usage Example:
// DWORD dwN = 0;
// LocalDBGetInstances(NULL, &dwN);
// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName));
// LocalDBGetInstances(insts, &dwN);
// for (int i = 0; i < dwN; i++)
// wprintf(L"%s\n", insts[i]);
//
// Return values:
// S_OK, if the function succeeds
//
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBGetInstances LocalDBGetInstances;
// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15
#define MAX_STRING_SID_LENGTH 186
#pragma pack(push)
#pragma pack(8)
// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file.
//
typedef struct _LocalDBInstanceInfo
{
DWORD cbLocalDBInstanceInfoSize;
TLocalDBInstanceName wszInstanceName;
BOOL bExists;
BOOL bConfigurationCorrupted;
BOOL bIsRunning;
DWORD dwMajor;
DWORD dwMinor;
DWORD dwBuild;
DWORD dwRevision;
FILETIME ftLastStartDateUTC;
WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE];
BOOL bIsShared;
TLocalDBInstanceName wszSharedInstanceName;
WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1];
BOOL bIsAutomatic;
} LocalDBInstanceInfo;
#pragma pack(pop)
typedef LocalDBInstanceInfo* PLocalDBInstanceInfo;
// type definition for LocalDBGetInstanceInfo function
typedef HRESULT __cdecl FnLocalDBGetInstanceInfo(
// I the LocalDB instance name
__in_z PCWSTR wszInstanceName,
// O instance information
__out PLocalDBInstanceInfo pInfo,
// I Size of LocalDBInstanceInfo structure in bytes
__in DWORD cbInfo);
// type definition for pointer to LocalDBGetInstances function
typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo;
// Function: LocalDBGetInstanceInfo
//
// Description: This function returns information about the given instance.
//
// Return values:
// S_OK, if the function succeeds
//
// ERROR_INVALID_PARAMETER, if some of the parameters is invalid
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details
//
FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo;
// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots
//
#define MAX_LOCALDB_VERSION_LENGTH 43
typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1];
typedef TLocalDBVersion* PTLocalDBVersion;
// type definition for LocalDBGetVersions function
typedef HRESULT __cdecl FnLocalDBGetVersions(
// O buffer for installed LocalDB versions
__out PTLocalDBVersion pVersions,
// I/O on input has the number slots for versions in the pVersions buffer. On output,
// has the number of existing LocalDB versions
__inout LPDWORD lpdwNumberOfVersions
);
// type definition for pointer to LocalDBGetVersions function
typedef FnLocalDBGetVersions* PFnLocalDBGetVersions;
// Function: LocalDBGetVersions
//
// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor
//
// Usage Example:
// DWORD dwN = 0;
// LocalDBGetVersions(NULL, &dwN);
// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion));
// LocalDBGetVersions(insts, &dwN);
// for (int i = 0; i < dwN; i++)
// wprintf(L"%s\n", insts[i]);
//
// Return values:
// S_OK, if the function succeeds
//
// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small
// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs.
//
FnLocalDBGetVersions LocalDBGetVersions;
#pragma pack(push)
#pragma pack(8)
// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file.
//
typedef struct _LocalDBVersionInfo
{
DWORD cbLocalDBVersionInfoSize;
TLocalDBVersion wszVersion;
BOOL bExists;
DWORD dwMajor;
DWORD dwMinor;
DWORD dwBuild;
DWORD dwRevision;
} LocalDBVersionInfo;
#pragma pack(pop)
typedef LocalDBVersionInfo* PLocalDBVersionInfo;
// type definition for LocalDBGetVersionInfo function
typedef HRESULT __cdecl FnLocalDBGetVersionInfo(
// I LocalDB version string
__in_z PCWSTR wszVersion,
// O version information
__out PLocalDBVersionInfo pVersionInfo,
// I Size of LocalDBVersionInfo structure in bytes
__in DWORD cbVersionInfo
);
// type definition for pointer to LocalDBGetVersionInfo function
typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo;
// Function: LocalDBGetVersionInfo
//
// Description: This function returns information about the given LocalDB version
//
// Return values:
// S_OK, if the function succeeds
// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred
// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid
//
FnLocalDBGetVersionInfo LocalDBGetVersionInfo;
typedef HRESULT __cdecl FnLocalDBStartTracing();
typedef FnLocalDBStartTracing* PFnLocalDBStartTracing;
// Function: LocalDBStartTracing
//
// Description: This function will write in registry that Tracing sessions should be started for the current user.
//
// Return values:
// S_OK - on success
// Propper HRESULT in case of failure
//
FnLocalDBStartTracing LocalDBStartTracing;
typedef HRESULT __cdecl FnLocalDBStopTracing();
typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing;
// Function: LocalDBStopTracing
//
// Description: This function will write in registry that Tracing sessions should be stopped for the current user.
//
// Return values:
// S_OK - on success
// Propper HRESULT in case of failure
//
FnLocalDBStopTracing LocalDBStopTracing;
// type definition for LocalDBShareInstance function
typedef HRESULT __cdecl FnLocalDBShareInstance(
// I the SID of the LocalDB instance owner
__in_opt PSID pOwnerSID,
// I the private name of LocalDB instance which should be shared
__in_z PCWSTR wszPrivateLocalDBInstanceName,
// I the public shared name
__in_z PCWSTR wszSharedName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags);
// type definition for pointer to LocalDBShareInstance function
typedef FnLocalDBShareInstance* PFnLocalDBShareInstance;
// Function: LocalDBShareInstance
//
// Description: This function will share the given private instance of the given user with the given shared name.
// This function has to be executed elevated.
//
// Return values:
// HRESULT
//
FnLocalDBShareInstance LocalDBShareInstance;
// type definition for LocalDBUnshareInstance function
typedef HRESULT __cdecl FnLocalDBUnshareInstance(
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags);
// type definition for pointer to LocalDBUnshareInstance function
typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance;
// Function: LocalDBUnshareInstance
//
// Description: This function unshares the given LocalDB instance.
// If a shared name is given then that shared instance will be unshared.
// If a private name is given then we will check if the caller
// shares a private instance with the given private name and unshare it.
//
// Return values:
// HRESULT
//
FnLocalDBUnshareInstance LocalDBUnshareInstance;
#ifdef __cplusplus
} // extern "C"
#endif
#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS)
//---------------------------------------------------------------------
// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS
// is defined. It provides an implementation of proxies for each of the LocalDB APIs.
// The proxy implementations use a common function to bind to entry points in the
// latest installed SqlUserInstance DLL, and then forward the requests.
//
// The current implementation loads the SqlUserInstance DLL on the first call into
// a proxy function. There is no provision for unloading the DLL. Note that if the
// process includes multiple binaries (EXE and one or more DLLs), each of them could
// load a separate instance of the SqlUserInstance DLL.
//
// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically.
//
// WARNING: these functions must not be called in DLL initialization, since a deadlock
// could result loading dependent DLLs.
//---------------------------------------------------------------------
// This macro provides the body for each proxy function.
//
#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn)
// Structure and function to parse the "Installed Versions" registry subkeys
//
typedef struct {
DWORD dwComponent[2];
WCHAR wszKeyName[256];
} Version;
// The following algorithm is intended to match, in part, the .NET Version class.
// A maximum of two components are allowed, which must be separated with a period.
// Valid: "11", "11.0"
// Invalid: "", ".0", "11.", "11.0."
//
static BOOL ParseVersion(Version * pVersion)
{
pVersion->dwComponent[0] = 0;
pVersion->dwComponent[1] = 0;
WCHAR * pwch = pVersion->wszKeyName;
for (int i = 0; i<2; i++)
{
LONGLONG llVal = 0;
BOOL fHaveDigit = FALSE;
while (*pwch >= L'0' && *pwch <= L'9')
{
llVal = llVal * 10 + (*pwch++ - L'0');
fHaveDigit = TRUE;
if (llVal > 0x7fffffff)
{
return FALSE;
}
}
if (!fHaveDigit)
return FALSE;
pVersion->dwComponent[i] = (DWORD) llVal;
if (*pwch == L'\0')
return TRUE;
if (*pwch != L'.')
return FALSE;
pwch++;
}
// If we get here, the version string was terminated with L'.', which is not valid
//
return FALSE;
}
#include <assert.h>
// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure.
// Note that the first-loaded API DLL for the process will be used until process termination: installation of
// a new version of the API will not be recognized after first load.
//
static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn)
{
static volatile HMODULE hLocalDBDll = NULL;
if (!hLocalDBDll)
{
LONG ec;
HKEY hkeyVersions = NULL;
HKEY hkeyVersion = NULL;
Version verHigh = {0};
Version verCurrent;
DWORD cchKeyName;
DWORD dwValueType;
WCHAR wszLocalDBDll[MAX_PATH+1];
DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk
HMODULE hLocalDBDllTemp = NULL;
if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions)))
{
goto Cleanup;
}
for (int i = 0; ; i++)
{
cchKeyName = 256;
if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL)))
{
if (ERROR_NO_MORE_ITEMS == ec)
{
break;
}
goto Cleanup;
}
if (!ParseVersion(&verCurrent))
{
continue; // invalid version syntax
}
if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] ||
(verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1]))
{
verHigh = verCurrent;
}
}
if (!verHigh.wszKeyName[0])
{
// ec must be ERROR_NO_MORE_ITEMS here
//
assert(ec == ERROR_NO_MORE_ITEMS);
// We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that
// LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists
// but it is empty.
//
ec = ERROR_FILE_NOT_FOUND;
goto Cleanup;
}
if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion)))
{
goto Cleanup;
}
if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll)))
{
goto Cleanup;
}
if (dwValueType != REG_SZ)
{
ec = ERROR_INVALID_DATA;
goto Cleanup;
}
// Ensure string value null-terminated
// Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose
//
wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0';
hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll);
if (NULL == hLocalDBDllTemp)
{
ec = GetLastError();
goto Cleanup;
}
if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL))
{
// We were the winner: we gave away our DLL handle
//
hLocalDBDllTemp = NULL;
}
ec = ERROR_SUCCESS;
Cleanup:
if (hLocalDBDllTemp)
FreeLibrary(hLocalDBDllTemp);
if (hkeyVersion)
RegCloseKey(hkeyVersion);
if (hkeyVersions)
RegCloseKey(hkeyVersions);
// Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing.
// In that case we should return the LocalDB specific error code
//
if (ec == ERROR_FILE_NOT_FOUND)
return LOCALDB_ERROR_NOT_INSTALLED;
if (ec != ERROR_SUCCESS)
return HRESULT_FROM_WIN32(ec);
}
FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn);
if (!pfn)
{
return HRESULT_FROM_WIN32(GetLastError());
}
*pfnLocalDBFn = pfn;
return S_OK;
}
// The following proxy functions forward calls to the latest LocalDB API DLL.
//
HRESULT __cdecl
LocalDBCreateInstance (
// I the LocalDB version (e.g. 11.0 or 11.0.1094.2)
__in_z PCWSTR wszVersion,
// I the instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
)
{
LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags);
}
HRESULT __cdecl
LocalDBStartInstance(
// I the instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags,
// O the buffer to store the connection string to the LocalDB instance
__out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection,
// I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null.
__inout_opt LPDWORD lpcchSqlConnection
)
{
LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection);
}
HRESULT __cdecl
LocalDBStopInstance (
// I the instance name
__in_z PCWSTR pInstanceName,
// I Available flags:
// LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately
// LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option
__in DWORD dwFlags,
// I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately
// without waiting for LocalDB instance to stop
__in ULONG ulTimeout
)
{
LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout);
}
HRESULT __cdecl
LocalDBDeleteInstance (
// I the instance name
__in_z PCWSTR pInstanceName,
// reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags
)
{
LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags);
}
HRESULT __cdecl
LocalDBFormatMessage(
// I the LocalDB error code
__in HRESULT hrLocalDB,
// I Available flags:
// LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short,
// the error message will be truncated to fit into the buffer
__in DWORD dwFlags,
// I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used)
__in DWORD dwLanguageId,
// O the buffer to store the LocalDB error message
__out_ecount_z(*lpcchMessage) LPWSTR wszMessage,
// I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is
// too small, has the buffer size required, in characters, including trailing null. If the function succeeds
// contains the number of characters in the message, excluding the trailing null
__inout LPDWORD lpcchMessage
)
{
LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage);
}
HRESULT __cdecl
LocalDBGetInstances(
// O buffer with instance names
__out PTLocalDBInstanceName pInstanceNames,
// I/O on input has the number slots for instance names in the pInstanceNames buffer. On output,
// has the number of existing LocalDB instances
__inout LPDWORD lpdwNumberOfInstances
)
{
LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances);
}
HRESULT __cdecl
LocalDBGetInstanceInfo(
// I the instance name
__in_z PCWSTR wszInstanceName,
// O instance information
__out PLocalDBInstanceInfo pInfo,
// I Size of LocalDBInstanceInfo structure in bytes
__in DWORD cbInfo
)
{
LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo);
}
HRESULT __cdecl
LocalDBStartTracing()
{
LOCALDB_PROXY(LocalDBStartTracing)();
}
HRESULT __cdecl
LocalDBStopTracing()
{
LOCALDB_PROXY(LocalDBStopTracing)();
}
HRESULT __cdecl
LocalDBShareInstance(
// I the SID of the LocalDB instance owner
__in_opt PSID pOwnerSID,
// I the private name of LocalDB instance which should be shared
__in_z PCWSTR wszLocalDBInstancePrivateName,
// I the public shared name
__in_z PCWSTR wszSharedName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags)
{
LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags);
}
HRESULT __cdecl
LocalDBGetVersions(
// O buffer for installed LocalDB versions
__out PTLocalDBVersion pVersions,
// I/O on input has the number slots for versions in the pVersions buffer. On output,
// has the number of existing LocalDB versions
__inout LPDWORD lpdwNumberOfVersions
)
{
LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions);
}
HRESULT __cdecl
LocalDBUnshareInstance(
// I the LocalDB instance name
__in_z PCWSTR pInstanceName,
// I reserved for the future use. Currently should be set to 0.
__in DWORD dwFlags)
{
LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags);
}
HRESULT __cdecl
LocalDBGetVersionInfo(
// I LocalDB version string
__in_z PCWSTR wszVersion,
// O version information
__out PLocalDBVersionInfo pVersionInfo,
// I Size of LocalDBVersionInfo structure in bytes
__in DWORD cbVersionInfo)
{
LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo);
}
#endif
#endif // _SQLUSERINSTANCE_H_
//-----------------------------------------------------------------------------
// File: sqluserinstancemsgs.mc
//
// Copyright: Copyright (c) Microsoft Corporation
//-----------------------------------------------------------------------------
#ifndef _LOCALDB_MESSAGES_H_
#define _LOCALDB_MESSAGES_H_
// Header section
//
// Section with the LocalDB messages
//
//
// Values are 32 bit values laid out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +-+-+-+-+-+---------------------+-------------------------------+
// |S|R|C|N|r| Facility | Code |
// +-+-+-+-+-+---------------------+-------------------------------+
//
// where
//
// S - Severity - indicates success/fail
//
// 0 - Success
// 1 - Fail (COERROR)
//
// R - reserved portion of the facility code, corresponds to NT's
// second severity bit.
//
// C - reserved portion of the facility code, corresponds to NT's
// C field.
//
// N - reserved portion of the facility code. Used to indicate a
// mapped NT status value.
//
// r - reserved portion of the facility code. Reserved for internal
// use. Used to indicate HRESULT values that are not status
// values, but are instead message ids for display strings.
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
//
// Define the facility codes
//
#define FACILITY_LOCALDB 0x9C5
//
// Define the severity codes
//
#define LOCALDB_SEVERITY_SUCCESS 0x0
#define LOCALDB_SEVERITY_ERROR 0x2
//
// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER
//
// MessageText:
//
// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>.
//
#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L)
//
// MessageId: LOCALDB_ERROR_INVALID_PARAMETER
//
// MessageText:
//
// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation.
//
#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L)
//
// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION
//
// MessageText:
//
// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version.
//
#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L)
//
// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER
//
// MessageText:
//
// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%).
//
#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L)
//
// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG
//
// MessageText:
//
// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>.
//
#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L)
//
// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER
//
// MessageText:
//
// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>.
//
#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L)
//
// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY
//
// MessageText:
//
// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE
//
// MessageText:
//
// The specified LocalDB instance does not exist.
//
#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L)
//
// MessageId: LOCALDB_ERROR_INTERNAL_ERROR
//
// MessageText:
//
// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L)
//
// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY
//
// MessageText:
//
// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L)
//
// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED
//
// MessageText:
//
// Error occurred during LocalDB instance startup: SQL Server process failed to start.
//
#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT
//
// MessageText:
//
// LocalDB instance is corrupted. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL)
//
// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS
//
// MessageText:
//
// Error occurred during LocalDB instance startup: unable to create the SQL Server process.
//
#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION
//
// MessageText:
//
// The specified LocalDB version is not available on this computer.
//
#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID
//
// MessageText:
//
// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown.
//
#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED
//
// MessageText:
//
// Stop operation for LocalDB instance failed to complete within the specified time.
//
#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL)
//
// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE
//
// MessageText:
//
// Error getting the localized error message. The specified error code is unknown.
//
#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L)
//
// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED
//
// MessageText:
//
// The LocalDB version available on this workstation is lower than the requested LocalDB version.
//
#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L)
//
// MessageId: LOCALDB_ERROR_INSTANCE_BUSY
//
// MessageText:
//
// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again.
//
#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L)
//
// MessageId: LOCALDB_ERROR_INVALID_OPERATION
//
// MessageText:
//
// Default LocalDB instances cannot be created, stopped or deleted manually.
//
#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L)
//
// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER
//
// MessageText:
//
// The buffer passed to the LocalDB instance API method has insufficient size.
//
#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L)
//
// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT
//
// MessageText:
//
// Timeout occurred inside the LocalDB instance API method.
//
#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L)
// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED.
// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API.
//
//
// MessageId: LOCALDB_ERROR_XEVENT_FAILED
//
// MessageText:
//
// Failed to start XEvent engine within the LocalDB Instance API.
//
#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L)
//
// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED
//
// MessageText:
//
// Cannot create an automatic instance. See the Windows Application event log for error details.
//
#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L)
//
// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN
//
// MessageText:
//
// Cannot create a shared instance. The specified shared instance name is already in use.
//
#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L)
//
// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER
//
// MessageText:
//
// API caller is not LocalDB instance owner.
//
#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL)
//
// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME
//
// MessageText:
//
// Specified LocalDB instance name is invalid.
//
#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED
//
// MessageText:
//
// The specified LocalDB instance is already shared with different shared name.
//
#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL)
//
// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED
//
// MessageText:
//
// The specified LocalDB instance is not shared.
//
#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL)
//
// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED
//
// MessageText:
//
// Administrator privileges are required in order to execute this operation.
//
#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL)
//
// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES
//
// MessageText:
//
// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances.
//
#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL)
//
// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH
//
// MessageText:
//
// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user.
//
#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L)
//
// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES
//
// MessageText:
//
// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL".
//
#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L)
// Detailed error descriptions
//
// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING
//
// MessageText:
//
// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L)
//
// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER
//
// MessageText:
//
// Cannot access LocalDB instance folder: %1
//
#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L)
//
// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG
//
// MessageText:
//
// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L)
//
// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING
//
// MessageText:
//
// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L)
//
// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG
//
// MessageText:
//
// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1
//
#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L)
//
// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID
//
// MessageText:
//
// Data directory for LocalDB instance is invalid: %1
//
#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT
//
// MessageText:
//
// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4)
//
#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR
//
// MessageText:
//
// LocalDB instance API: XEvent error: %1
//
#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L)
//
// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED
//
// MessageText:
//
// LocalDB installation is corrupted. Reinstall the LocalDB.
//
#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L)
//
// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION
//
// MessageText:
//
// LocalDB XEvent error: cannot determine %ProgramFiles% folder location.
//
#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE
//
// MessageText:
//
// LocalDB XEvent error: Cannot initialize XEvent engine.
//
#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE
//
// MessageText:
//
// LocalDB XEvent error: Cannot find XEvents configuration file: %1
//
#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE
//
// MessageText:
//
// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1
// HRESULT returned: %2
//
#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL)
//
// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG
//
// MessageText:
//
// LocalDB XEvent error: XEvents engine configuration file too long
//
#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL)
//
// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED
//
// MessageText:
//
// CoInitializeEx API failed. HRESULT returned: %1
//
#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL)
//
// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID
//
// MessageText:
//
// LocalDB parent instance version is invalid: %1
//
#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL)
//
// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR
//
// MessageText:
//
// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5
//
#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L)
//
// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT
//
// MessageText:
//
// Function %1 returned %2 at line %3.
//
#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L)
//
#endif // _LOCALDB_MESSAGES_H_
#endif //__msodbcsql_h__

View file

@ -1,635 +0,0 @@
#ifndef PHP_SQLSRV_H
#define PHP_SQLSRV_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_sqlsrv.h
//
// Contents: Declarations for the extension
//
// Comments: Also contains "internal" declarations shared across source files.
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include "version.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef PHP_WIN32
#define PHP_SQLSRV_API __declspec(dllexport)
#else
#define PHP_SQLSRV_API
#endif
// OACR is an internal Microsoft static code analysis tool
#if defined(OACR)
#include <oacr.h>
OACR_WARNING_PUSH
OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." )
OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." )
OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." )
OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." )
OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." )
#endif
extern "C" {
#pragma warning(push)
#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 )
#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
#include "php.h"
#include "php_globals.h"
#include "php_ini.h"
#include "ext/standard/php_standard.h"
#include "ext/standard/info.h"
#pragma warning(pop)
#if PHP_MAJOR_VERSION > 5 || PHP_MAJOR_VERSION < 5 || ( PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 )
#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP
#endif
#if ZEND_DEBUG
// debug build causes warning C4505 to pop up from the Zend header files
#pragma warning( disable: 4505 )
#endif
} // extern "C"
//*********************************************************************************************************************************
// Initialization Functions
//*********************************************************************************************************************************
// module global variables (initialized in minit and freed in mshutdown)
extern HashTable* g_ss_errors_ht;
extern HashTable* g_ss_encodings_ht;
extern HashTable* g_ss_warnings_to_ignore_ht;
// variables set during initialization
extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP
extern HMODULE g_sqlsrv_hmodule; // used for getting the version information
// henv context for creating connections
extern sqlsrv_context* g_henv_cp;
extern sqlsrv_context* g_henv_ncp;
extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in
// module initialization
PHP_MINIT_FUNCTION(sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(sqlsrv);
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
PHP_FUNCTION(sqlsrv_connect);
PHP_FUNCTION(sqlsrv_begin_transaction);
PHP_FUNCTION(sqlsrv_client_info);
PHP_FUNCTION(sqlsrv_close);
PHP_FUNCTION(sqlsrv_commit);
PHP_FUNCTION(sqlsrv_query);
PHP_FUNCTION(sqlsrv_prepare);
PHP_FUNCTION(sqlsrv_rollback);
PHP_FUNCTION(sqlsrv_server_info);
struct ss_sqlsrv_conn : sqlsrv_conn
{
HashTable* stmts;
bool date_as_string;
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
// static variables used in process_params
static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here.
static int descriptor;
// initialize with default values
ss_sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ) :
sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ),
stmts( NULL ),
date_as_string( false ),
in_transaction( false )
{
}
};
// resource destructor
void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field_name {
char* name;
unsigned int len;
};
struct stmt_option_scrollable : public stmt_option_functor {
virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC );
};
// This object inherits and overrides the callbacks necessary
struct ss_sqlsrv_stmt : public sqlsrv_stmt {
ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC );
virtual ~ss_sqlsrv_stmt( void );
void new_result_set( TSRMLS_D );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream );
bool prepared; // whether the statement has been prepared yet (used for error messages)
int conn_index; // index into the connection hash that contains this statement structure
zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute
sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys
int fetch_fields_count;
// static variables used in process_params
static char* resource_name; // char because const char forces casting all over the place in ODBC functions
static int descriptor;
};
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field {
char* name;
unsigned int len;
};
// holds the stream param and the encoding that it was assigned
struct sqlsrv_stream_encoding {
zval* stream_z;
unsigned int encoding;
sqlsrv_stream_encoding( zval* str_z, unsigned int enc ) :
stream_z( str_z ), encoding( enc )
{
}
};
// *** statement functions ***
PHP_FUNCTION(sqlsrv_cancel);
PHP_FUNCTION(sqlsrv_execute);
PHP_FUNCTION(sqlsrv_fetch);
PHP_FUNCTION(sqlsrv_fetch_array);
PHP_FUNCTION(sqlsrv_fetch_object);
PHP_FUNCTION(sqlsrv_field_metadata);
PHP_FUNCTION(sqlsrv_free_stmt);
PHP_FUNCTION(sqlsrv_get_field);
PHP_FUNCTION(sqlsrv_has_rows);
PHP_FUNCTION(sqlsrv_next_result);
PHP_FUNCTION(sqlsrv_num_fields);
PHP_FUNCTION(sqlsrv_num_rows);
PHP_FUNCTION(sqlsrv_rows_affected);
PHP_FUNCTION(sqlsrv_send_stream_data);
// resource destructor
void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
// "internal" statement functions shared by functions in conn.cpp and stmt.cpp
void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC );
void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC );
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function
TSRMLS_DC );
void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC );
void free_stmt_resource( zval* stmt_z TSRMLS_DC );
//*********************************************************************************************************************************
// Type Functions
//*********************************************************************************************************************************
// type functions for SQL types.
// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc.
// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added.
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY);
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL);
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC);
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY);
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR);
// PHP type functions
// strings and streams may have an encoding parameterized, so we use the functions
// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR.
PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM);
PHP_FUNCTION(SQLSRV_PHPTYPE_STRING);
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
extern "C" {
// request level variables
ZEND_BEGIN_MODULE_GLOBALS(sqlsrv)
// global objects for errors and warnings. These are returned by sqlsrv_errors.
zval* errors;
zval* warnings;
// flags for error handling and logging (set via sqlsrv_configure or php.ini)
long log_severity;
long log_subsystems;
long current_subsystem;
zend_bool warnings_return_as_errors;
long buffered_query_limit;
ZEND_END_MODULE_GLOBALS(sqlsrv)
ZEND_EXTERN_MODULE_GLOBALS(sqlsrv);
}
// macros used to access the global variables. Use these to make global variable access agnostic to threads
#ifdef ZTS
#define SQLSRV_G(v) TSRMG(sqlsrv_globals_id, zend_sqlsrv_globals *, v)
#else
#define SQLSRV_G(v) sqlsrv_globals.v
#endif
// INI settings and constants
// (these are defined as macros to allow concatenation as we do below)
#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors"
#define INI_LOG_SEVERITY "LogSeverity"
#define INI_LOG_SUBSYSTEMS "LogSubsystems"
#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize"
#define INI_PREFIX "sqlsrv."
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors,
zend_sqlsrv_globals, sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals,
sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals,
sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit,
zend_sqlsrv_globals, sqlsrv_globals )
PHP_INI_END()
//*********************************************************************************************************************************
// Configuration
//*********************************************************************************************************************************
// These functions set and retrieve configuration settings. Configuration settings defined are:
// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs.
// LogSeverity - combination of severity of messages to log (see Logging)
// LogSubsystems - subsystems within sqlsrv to log messages (see Logging)
PHP_FUNCTION(sqlsrv_configure);
PHP_FUNCTION(sqlsrv_get_config);
//*********************************************************************************************************************************
// Errors
//*********************************************************************************************************************************
// represents the mapping between an error_code and the corresponding error message.
struct ss_error {
unsigned int error_code;
sqlsrv_error_const sqlsrv_error;
};
// List of all driver specific error codes.
enum SS_ERROR_CODES {
SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC,
SS_SQLSRV_ERROR_NOT_IN_TXN,
SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER,
SS_SQLSRV_ERROR_REGISTER_RESOURCE,
SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY,
SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE,
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE,
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED,
SS_SQLSRV_ERROR_ZEND_BAD_CLASS,
SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE,
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE,
SS_SQLSRV_ERROR_INVALID_OPTION,
SS_SQLSRV_ERROR_PARAM_INVALID_INDEX,
SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION,
SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION,
SS_SQLSRV_ERROR_VAR_REQUIRED,
SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING,
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
};
extern ss_error SS_ERRORS[];
bool ss_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args );
// *** extension error functions ***
PHP_FUNCTION(sqlsrv_errors);
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string.
unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string,
unsigned int mbcs_len, __out wchar_t* utf16_out_string,
unsigned int utf16_len );
// create a wide char string from the passed in mbcs string. NULL is returned if the string
// could not be created. No error is posted by this function. utf16_len is the number of
// wchar_t characters, not the number of bytes.
wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string,
unsigned int mbcs_len, __out unsigned int* utf16_len );
// *** internal error macros and functions ***
bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp TSRMLS_DC, ... );
void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp TSRMLS_DC, ... );
void __cdecl sqlsrv_error_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC );
// release current error lists and set to NULL
inline void reset_errors( TSRMLS_D )
{
if( Z_TYPE_P( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE_P( SQLSRV_G( errors )) != IS_NULL ) {
DIE( "sqlsrv_errors contains an invalid type" );
}
if( Z_TYPE_P( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE_P( SQLSRV_G( warnings )) != IS_NULL ) {
DIE( "sqlsrv_warnings contains an invalid type" );
}
if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL_P( SQLSRV_G( errors )));
FREE_HASHTABLE( Z_ARRVAL_P( SQLSRV_G( errors )));
}
if( Z_TYPE_P( SQLSRV_G( warnings )) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL_P( SQLSRV_G( warnings )));
FREE_HASHTABLE( Z_ARRVAL_P( SQLSRV_G( warnings )));
}
ZVAL_NULL( SQLSRV_G( errors ));
ZVAL_NULL( SQLSRV_G( warnings ));
}
#define THROW_SS_ERROR( ctx, error_code, ... ) \
(void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, __VA_ARGS__ ); \
throw ss::SSException();
class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > {
public:
sqlsrv_context_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_context, sqlsrv_context_auto_ptr >( NULL )
{
}
sqlsrv_context_auto_ptr( const sqlsrv_context_auto_ptr& src ) :
sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( sqlsrv_context* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_context();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_context* operator=( sqlsrv_context* ptr )
{
return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr );
}
void operator=( sqlsrv_context_auto_ptr& src )
{
sqlsrv_context* p = src.get();
src.transferred();
this->_ptr = p;
}
};
//*********************************************************************************************************************************
// Logging
//*********************************************************************************************************************************
#define LOG_FUNCTION( function_name ) \
const char* _FN_ = function_name; \
SQLSRV_G( current_subsystem ) = current_log_subsystem; \
LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); \
CheckMemory _check_memory_;
#define SET_FUNCTION_NAME( context ) \
{ \
(context).set_func( _FN_ ); \
}
// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro
void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args );
// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise.
enum logging_subsystems {
LOG_INIT = 0x01,
LOG_CONN = 0x02,
LOG_STMT = 0x04,
LOG_UTIL = 0x08,
LOG_ALL = -1,
};
struct CheckMemory {
CheckMemory( void )
{
// test the integrity of the Zend heap.
full_mem_check(MEMCHECK_SILENT);
}
~CheckMemory( void )
{
// test the integrity of the Zend heap.
full_mem_check(MEMCHECK_SILENT);
}
};
//*********************************************************************************************************************************
// Utility Functions
//*********************************************************************************************************************************
// generic function used to validate parameters to a PHP function.
// Register an invalid parameter error and returns NULL when parameters don't match the spec given.
template <typename H>
inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, int param_count, ... )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
SQLSRV_UNUSED( return_value );
zval* rsrc;
H* h;
// reset the errors from the previous API call
reset_errors( TSRMLS_C );
if( ZEND_NUM_ARGS() > param_count + 1 ) {
DIE( "Param count and argument count don't match." );
return NULL; // for static analysis tools
}
try {
if( param_count > 6 ) {
DIE( "Param count cannot exceed 6" );
return NULL; // for static analysis tools
}
void* arr[6];
va_list vaList;
va_start(vaList, param_count); //set the pointer to first argument
for(int i=0; i< param_count; ++i) {
arr[i] = va_arg(vaList, void*);
}
va_end(vaList);
int result = SUCCESS;
// dummy context to pass to the error handler
sqlsrv_context error_ctx( 0, ss_error_handler, NULL );;
error_ctx.set_func( calling_func );
switch( param_count ) {
case 0:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc );
break;
case 1:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc, arr[0] );
break;
case 2:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1] );
break;
case 3:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2] );
break;
case 4:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3] );
break;
case 5:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3], arr[4] );
break;
case 6:
result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3], arr[4], arr[5] );
break;
default:
{
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func );
break;
}
}
CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) {
throw ss::SSException();
}
// get the resource registered
h = static_cast<H*>( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor ));
CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) {
throw ss::SSException();
}
h->set_func( calling_func );
return h;
}
catch( core::CoreException& ) {
return NULL;
}
catch ( ... ) {
DIE( "%1!s!: Unknown exception caught in process_params.", calling_func );
}
}
//*********************************************************************************************************************************
// Common function wrappers
//*********************************************************************************************************************************
namespace ss {
// an error which occurred in our SQLSRV driver
struct SSException : public core::CoreException {
SSException()
{
}
};
inline void zend_register_resource( __out zval* rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC )
{
int zr = ZEND_REGISTER_RESOURCE( rsrc_result, rsrc_pointer, rsrc_type );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast<sqlsrv_context*>( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE,
rsrc_name ) {
throw ss::SSException();
}
}
} // namespace ss
#endif /* PHP_SQLSRV_H */

View file

@ -1,2274 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: stmt.cpp
//
// Contents: Routines that use statement handles
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
// *** header files ***
#include "php_sqlsrv.h"
#include <sal.h>
//
// *** internal variables and constants ***
//
// our resource descriptor assigned in minit
int ss_sqlsrv_stmt::descriptor = 0;
char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt"; // not const for a reason. see sqlsrv_stmt in php_sqlsrv.h
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_STMT;
// constants used as invalid types for type errors
const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID;
const int SQLTYPE_INVALID = 0;
const int SQLSRV_INVALID_PRECISION = -1;
const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U);
const int SQLSRV_INVALID_SCALE = -1;
const int SQLSRV_SIZE_MAX_TYPE = -1;
// constants for maximums in SQL Server
const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
const int SQL_SERVER_MAX_PRECISION = 38;
const int SQL_SERVER_DEFAULT_PRECISION = 18;
const int SQL_SERVER_DEFAULT_SCALE = 0;
// default class used when no class is specified by sqlsrv_fetch_object
const char STDCLASS_NAME[] = "stdclass";
const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1;
// map a Zend PHP type constant to our constant type
enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
SQLSRV_PHPTYPE_NULL,
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_STREAM,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID
};
// constant strings used for the field metadata results
// (char to avoid having to cast them where they are used)
namespace FieldMetaData {
char* NAME = "Name";
char* TYPE = "Type";
char* SIZE = "Size";
char* PREC = "Precision";
char* SCALE = "Scale";
char* NULLABLE = "Nullable";
}
// warning message printed when a parameter variable is not passed by reference
const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not passed by reference (prefaced with an &). "
"Variable parameters passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value. "
"For more information, see sqlsrv_prepare or sqlsrv_query in the API Reference section of the product documentation.";
/* internal functions */
zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len );
void fetch_fields_common( __inout ss_sqlsrv_stmt* stmt, int fetch_type, __out zval* return_value, bool allow_empty_field_names
TSRMLS_DC );
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, __out SQLUINTEGER* column_size,
__out SQLSMALLINT* decimal_digits );
sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string );
void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC );
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type );
bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type );
void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigned long index, __out int& direction,
__out SQLSRV_PHPTYPE& php_out_type, __out SQLSRV_ENCODING& encoding, __out SQLSMALLINT& sql_type,
__out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC );
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type );
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type );
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type );
bool verify_and_set_encoding( const char* encoding_string, __out sqlsrv_phptype& phptype_encoding TSRMLS_DC );
}
// query options for cursor types
namespace SSCursorTypes {
const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static";
const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic";
const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset";
const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward";
const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered";
}
ss_sqlsrv_stmt::ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) :
sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ),
prepared( false ),
conn_index( -1 ),
params_z( NULL ),
fetch_field_names( NULL ),
fetch_fields_count ( 0 )
{
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC );
}
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
{
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
sqlsrv_free( fetch_field_names[ i ].name );
}
sqlsrv_free( fetch_field_names );
}
if( params_z ) {
zval_ptr_dtor( &params_z );
params_z = NULL;
}
}
// to be called whenever a new result set is created, such as after an
// execute or next_result. Resets the state variables and calls the subclass.
void ss_sqlsrv_stmt::new_result_set( TSRMLS_D )
{
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
sqlsrv_free( fetch_field_names[ i ].name );
}
sqlsrv_free( fetch_field_names );
}
fetch_field_names = NULL;
fetch_fields_count = 0;
sqlsrv_stmt::new_result_set( TSRMLS_C );
}
// Returns a php type for a given sql type. Also sets the encoding wherever applicable.
sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream )
{
sqlsrv_phptype ss_phptype;
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
switch( sql_type ) {
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_WCHAR:
case SQL_SS_VARIANT:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
if( prefer_string_to_stream ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
break;
case SQL_FLOAT:
case SQL_REAL:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
if( reinterpret_cast<ss_sqlsrv_conn*>( this->conn )->date_as_string ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
default:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
break;
}
return ss_phptype;
}
// statement specific parameter proccessing. Uses the generic function specialised to return a statement
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
rsrc = process_params<ss_sqlsrv_stmt>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, __VA_ARGS__ );\
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
// sqlsrv_execute( resource $stmt )
//
// Executes a previously prepared statement. See sqlsrv_prepare for information
// on preparing a statement for execution.
//
// This function is ideal for executing a prepared statement multiple times with
// different parameter values. See the MSDN documentation
//
// Parameters
// $stmt: A resource specifying the statement to be executed. For more
// information about how to create a statement resource, see sqlsrv_prepare.
//
// Return Value
// A Boolean value: true if the statement was successfully executed. Otherwise, false.
PHP_FUNCTION( sqlsrv_execute )
{
LOG_FUNCTION( "sqlsrv_execute" );
ss_sqlsrv_stmt* stmt = NULL;
try {
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) {
throw ss::SSException();
}
// prepare for the next execution by flushing anything remaining in the result set
if( stmt->executed ) {
// to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too)
while( stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( stmt TSRMLS_CC, false, false );
}
}
// bind parameters before executing
bind_params( stmt TSRMLS_CC );
core_sqlsrv_execute( stmt TSRMLS_CC );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_execute: Unknown exception caught." );
}
}
// sqlsrv_fetch( resource $stmt )
//
// Makes the next row of a result set available for reading. Use
// sqlsrv_get_field to read fields of the row.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement. A
// statement must be executed before results can be retrieved. For information
// on executing a statement, see sqlsrv_query and sqlsrv_execute.
//
// Return Value
// If the next row of the result set was successfully retrieved, true is
// returned. If there are no more results in the result set, null is
// returned. If an error occured, false is returned
PHP_FUNCTION( sqlsrv_fetch )
{
LOG_FUNCTION( "sqlsrv_fetch" );
ss_sqlsrv_stmt* stmt = NULL;
int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
int fetch_offset = 0; // default value for parameter if one isn't supplied
// take only the statement resource
PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC );
if( !result ) {
RETURN_NULL();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch: Unknown exception caught." );
}
}
// sqlsrv_fetch_array( resource $stmt [, int $fetchType] )
//
// Retrieves the next row of data as an array.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h
//
// Return Value
// If a row of data is retrieved, an array is returned. If there are no more
// rows to retrieve, null is returned. If an error occurs, false is returned.
// Based on the value of the $fetchType parameter, the returned array can be a
// numerically indexed array, an associative array, or both. By default, an
// array with both numeric and associative keys is returned. The data type of a
// value in the returned array will be the default PHP data type. For
// information about default PHP data types, see Default PHP Data Types.
PHP_FUNCTION( sqlsrv_fetch_array )
{
LOG_FUNCTION( "sqlsrv_fetch_array" );
ss_sqlsrv_stmt* stmt = NULL;
int fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied
int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
int fetch_offset = 0; // default value for parameter if one isn't supplied
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC );
if( !result ) {
RETURN_NULL();
}
fetch_fields_common( stmt, fetch_type, return_value, true /*allow_empty_field_names*/ TSRMLS_CC );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch_array: Unknown exception caught." );
}
}
// sqlsrv_field_metadata( resource $stmt )
//
// Retrieves metadata for the fields of a prepared statement. For information
// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that
// sqlsrv_field_metadata can be called on any prepared statement, pre- or
// post-execution.
//
// Parameters
// $stmt: A statement resource for which field metadata is sought.
//
// Return Value
// retrieve an array of metadata for the current result set on a statement. Each element of the
// array is a sub-array containing 5 elements accessed by key:
// name - name of the field.
// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants.
// size - length of the field. null if the field uses precision and scale instead.
// precision - number of digits in a numeric field. null if the field uses size.
// scale - number of decimal digits in a numeric field. null if the field uses sizes.
// is_nullable - if the field may contain a NULL instead of a value
// false is returned if an error occurs retrieving the metadata
PHP_FUNCTION( sqlsrv_field_metadata )
{
sqlsrv_stmt* stmt = NULL;
SQLSMALLINT num_cols = -1;
LOG_FUNCTION( "sqlsrv_field_metadata" );
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// get the number of fields in the resultset
num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
zval_auto_ptr result_meta_data;
ALLOC_INIT_ZVAL( result_meta_data );
core::sqlsrv_array_init( *stmt, result_meta_data TSRMLS_CC );
for( SQLSMALLINT f = 0; f < num_cols; ++f ) {
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC );
// initialize the array
zval_auto_ptr field_array;
ALLOC_INIT_ZVAL( field_array );
core::sqlsrv_array_init( *stmt, field_array TSRMLS_CC );
core::sqlsrv_add_assoc_string( *stmt, field_array, FieldMetaData::NAME,
reinterpret_cast<char*>( core_meta_data->field_name.get() ), 0 TSRMLS_CC );
core_meta_data->field_name.transferred();
core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC );
switch( core_meta_data->field_type ) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SIZE TSRMLS_CC );
core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC );
core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC );
break;
case SQL_BIT:
case SQL_TINYINT:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_BIGINT:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SIZE TSRMLS_CC );
core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC );
core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SCALE TSRMLS_CC );
break;
default:
core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC );
core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::PREC TSRMLS_CC );
core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SCALE TSRMLS_CC );
break;
}
// add the nullability to the array
core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable
TSRMLS_CC );
// add this field's meta data to the result set meta data
core::sqlsrv_add_next_index_zval( *stmt, result_meta_data, field_array TSRMLS_CC );
field_array.transferred();
// always good to call destructor for allocations done through placement new operator.
core_meta_data->~field_meta_data();
}
// return our built collection and transfer ownership
zval_ptr_dtor( &return_value );
*return_value_ptr = result_meta_data;
result_meta_data.transferred();
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_field_metadata: Unknown exception caught." );
}
}
// sqlsrv_next_result( resource $stmt )
//
// Makes the next result (result set, row count, or output parameter) of the
// specified statement active. The first (or only) result returned by a batch
// query or stored procedure is active without a call to sqlsrv_next_result.
// Any output parameters bound are only available after sqlsrv_next_result returns
// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx
//
// Parameters
// $stmt: The executed statement on which the next result is made active.
//
// Return Value
// If the next result was successfully made active, the Boolean value true is
// returned. If an error occurred in making the next result active, false is
// returned. If no more results are available, null is returned.
PHP_FUNCTION( sqlsrv_next_result )
{
LOG_FUNCTION( "sqlsrv_next_result" );
SQLRETURN r = SQL_SUCCESS;
ss_sqlsrv_stmt* stmt = NULL;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
core_sqlsrv_next_result( stmt TSRMLS_CC, true );
if( stmt->past_next_result_end ) {
RETURN_NULL();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_next_result: Unknown exception caught." );
}
}
// sqlsrv_rows_affected( resource $stmt )
//
// Returns the number of rows modified by the last statement executed. This
// function does not return the number of rows returned by a SELECT statement.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// Return Value
// An integer indicating the number of rows modified by the last executed
// statement. If no rows were modified, zero (0) is returned. If no information
// about the number of modified rows is available, negative one (-1) is
// returned. If an error occurred in retrieving the number of modified rows,
// false is returned. See SQLRowCount in the MSDN ODBC documentation.
PHP_FUNCTION( sqlsrv_rows_affected )
{
LOG_FUNCTION( "sqlsrv_rows_affected" );
SQLRETURN r = SQL_SUCCESS;
ss_sqlsrv_stmt* stmt = NULL;
SQLINTEGER rows = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// make sure that the statement has already been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
// make sure it is not scrollable. This function should only work for inserts, updates, and deletes,
// but this is the best we can do to enforce that.
CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) {
throw ss::SSException();
}
rows = stmt->current_results->row_count( TSRMLS_C );
RETURN_LONG( rows );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_rows_affected: Unknown exception caught." );
}
}
// sqlsrv_num_rows( resource $stmt )
//
// Retrieves the number of rows in an active result set. The statement must
// have been created with the Scrollable attribute set to 'static'.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of rows in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_rows )
{
LOG_FUNCTION( "sqlsrv_num_rows" );
ss_sqlsrv_stmt* stmt = NULL;
SQLINTEGER rows = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// make sure that the statement has already been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
// make sure that the statement is scrollable and the cursor is not dynamic.
// if the cursor is dynamic, then the number of rows returned is always -1.
CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt,
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) {
throw ss::SSException();
}
rows = stmt->current_results->row_count( TSRMLS_C );
RETURN_LONG( rows );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_num_rows: Unknown exception caught." );
}
}
// sqlsrv_num_fields( resource $stmt )
//
// Retrieves the number of fields in an active result set. Note that
// sqlsrv_num_fields can be called on any prepared statement, before or after
// execution.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of fields in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_fields )
{
LOG_FUNCTION( "sqlsrv_num_fields" );
ss_sqlsrv_stmt* stmt = NULL;
SQLSMALLINT fields = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// retrieve the number of columns from ODBC
fields = core::SQLNumResultCols( stmt TSRMLS_CC );
RETURN_LONG( fields );
}
catch( ss::SSException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_num_fields: Unknown exception caught." );
}
}
// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]])
//
// Retrieves the next row of data as a PHP object.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// $className [OPTIONAL]: A string specifying the name of the class to
// instantiate. If a value for the $className parameter is not specified, an
// instance of the PHP stdClass is instantiated.
//
// $ctorParams [OPTIONAL]: An array that contains values passed to the
// constructor of the class specified with the $className parameter. If the
// constructor of the specified class accepts parameter values, the $ctorParams
// parameter must be used when calling sqlsrv_fetch_object.
//
// Return Value
// A PHP object with properties that correspond to result set field
// names. Property values are populated with the corresponding result set field
// values. If the class specified with the optional $className parameter does
// not exist or if there is no active result set associated with the specified
// statement, false is returned.
// The data type of a value in the returned object will be the default PHP data
// type. For information on default PHP data types, see Default PHP Data Types.
//
// Remarks
// If a class name is specified with the optional $className parameter, an
// object of this class type is instantiated. If the class has properties whose
// names match the result set field names, the corresponding result set values
// are applied to the properties. If a result set field name does not match a
// class property, a property with the result set field name is added to the
// object and the result set value is applied to the property. For more
// information about calling sqlsrv_fetch_object with the $className parameter,
// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server).
//
// If a field with no name is returned, sqlsrv_fetch_object will discard the
// field value and issue a warning.
PHP_FUNCTION( sqlsrv_fetch_object )
{
LOG_FUNCTION( "sqlsrv_fetch_object" );
ss_sqlsrv_stmt* stmt = NULL;
zval* class_name_z = NULL;
zval* ctor_params_z = NULL;
int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
int fetch_offset = 0; // default value for parameter if one isn't supplied
// stdClass is the name of the system's default base class in PHP
char* class_name = const_cast<char*>( STDCLASS_NAME );
unsigned int class_name_len = STDCLASS_NAME_LEN;
HashTable* properties_ht = NULL;
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
// we also use z! instead of s and a so that null may be passed in as valid values for
// the class name and ctor params
PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
if( class_name_z ) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
class_name = Z_STRVAL_P( class_name_z );
class_name_len = Z_STRLEN_P( class_name_z );
zend_str_tolower( class_name, class_name_len );
}
if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
// fetch the data
bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC );
if( !result ) {
RETURN_NULL();
}
fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, return_value, false /*allow_empty_field_names*/ TSRMLS_CC );
properties_ht = Z_ARRVAL_P( return_value );
// find the zend_class_entry of the class the user requested (stdClass by default) for use below
zend_class_entry** class_entry;
int zr = zend_lookup_class( class_name, class_name_len, &class_entry TSRMLS_CC );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) {
throw ss::SSException();
}
// create an instance of the object with its default properties
// we pass NULL for the properties so that the object will be populated by its default properties
zr = object_and_properties_init( return_value, *class_entry, NULL /*properties*/ );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
// merge in the "properties" (associative array) returned from the fetch doing this vice versa
// since putting properties_ht into object_and_properties_init and merging the default properties
// causes duplicate properties when the visibilities are different and also references the
// default parameters directly in the object, meaning the default property value is changed when
// the object's property is changed.
zend_merge_properties( return_value, properties_ht, 1 /*destroy_ht*/ TSRMLS_CC );
// find and call the object's constructor
// The header files (zend.h and zend_API.h) declare
// these functions and structures, so by working with those, we were able to
// develop this as a suitable snippet for calling constructors. Some observations:
// params must be an array of zval**, not a zval** to an array as we originally
// thought. Also, a constructor doesn't show up in the function table, but
// is put into the "magic methods" section of the class entry.
//
// The default values of the fci and fcic structures were determined by
// calling zend_fcall_info_init with a test callable.
// if there is a constructor (e.g., stdClass doesn't have one)
if( (*class_entry)->constructor ) {
// take the parameters given as our last argument and put them into a sequential array
sqlsrv_malloc_auto_ptr<zval**> params_m;
zval_auto_ptr ctor_retval_z;
ALLOC_INIT_ZVAL( ctor_retval_z );
int num_params = 0;
if( ctor_params_z != NULL ) {
HashTable* ctor_params_ht = Z_ARRVAL_P( ctor_params_z );
num_params = zend_hash_num_elements( ctor_params_ht );
params_m = reinterpret_cast<zval***>( sqlsrv_malloc( num_params * sizeof( zval**) ));
int i;
for( i = 0, zend_hash_internal_pointer_reset( ctor_params_ht );
zend_hash_has_more_elements( ctor_params_ht ) == SUCCESS;
zend_hash_move_forward( ctor_params_ht ), ++i ) {
zr = zend_hash_get_current_data_ex( ctor_params_ht, reinterpret_cast<void**>(&(params_m[ i ])), NULL );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
} //for
} //if( ctor_params_z != NULL )
// call the constructor function itself.
zend_fcall_info fci;
zend_fcall_info_cache fcic;
memset( &fci, 0, sizeof( fci ));
fci.size = sizeof( fci );
fci.function_table = &(*class_entry)->function_table;
fci.function_name = NULL;
fci.retval_ptr_ptr = &ctor_retval_z;
fci.param_count = num_params;
fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred.
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2
fci.object_pp = &return_value;
#else
fci.object_ptr = return_value;
#endif
memset( &fcic, 0, sizeof( fcic ));
fcic.initialized = 1;
fcic.function_handler = (*class_entry)->constructor;
fcic.calling_scope = *class_entry;
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2
fcic.object_pp = &return_value;
#else
fcic.object_ptr = return_value;
#endif
zr = zend_call_function( &fci, &fcic TSRMLS_CC );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
} //if( (*class_entry)->constructor )
}
catch( core::CoreException& ) {
if( properties_ht != NULL ) {
zend_hash_destroy( properties_ht );
FREE_HASHTABLE( properties_ht );
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch_object: Unknown exception caught." );
}
}
// sqlsrv_has_rows( resource $stmt )
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// Returns whether or not there are rows waiting to be processed. There are two scenarios
// for using a function like this:
// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this.
// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least
// one row of data.
// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as
// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed
// until sqlsrv_fetch returns NULL.
// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error
// most likely means that there is no result data anyways.
// If this functions returs true one time, then it will return true even after the result set is exhausted
// (sqlsrv_fetch returns null)
PHP_FUNCTION( sqlsrv_has_rows )
{
LOG_FUNCTION( "sqlsrv_has_rows" );
ss_sqlsrv_stmt* stmt = NULL;
try {
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
if( !stmt->fetch_called ) {
determine_stmt_has_rows( stmt TSRMLS_CC );
}
if( stmt->has_rows ) {
RETURN_TRUE;
}
}
catch( core::CoreException& ) {
}
catch( ... ) {
DIE( "sqlsrv_has_rows: Unknown exception caught." );
}
RETURN_FALSE;
}
// sqlsrv_send_stream_data( resource $stmt )
//
// Sends data from parameter streams to the server. Up to eight kilobytes (8K)
// of data is sent with each call to sqlsrv_send_stream_data.
// By default, all stream data is sent to the server when a query is
// executed. If this default behavior is not changed, you do not have to use
// sqlsrv_send_stream_data to send stream data to the server. For information
// about changing the default behavior, see the Parameters section of
// sqlsrv_query or sqlsrv_prepare.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// Return Value
// true if there is more data to be sent. null, if all the data has been sent,
// and false if an error occurred
PHP_FUNCTION( sqlsrv_send_stream_data )
{
sqlsrv_stmt* stmt = NULL;
LOG_FUNCTION( "sqlsrv_send_stream_data" );
// get the statement resource that we've bound streams to
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// if everything was sent at execute time, just return that there is nothing more to send.
if( stmt->send_streams_at_exec ) {
RETURN_NULL();
}
// send the next packet
bool more = core_sqlsrv_send_stream_packet( stmt TSRMLS_CC );
// if more to send, return true
if( more ) {
RETURN_TRUE;
}
// otherwise we're done, so return null
else {
RETURN_NULL();
}
}
catch( core::CoreException& ) {
// return false if an error occurred
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_send_stream_data: Unknown exception caught." );
}
}
// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] )
//
// Retrieves data from the specified field of the current row. Field data must
// be accessed in order. For example, data from the first field cannot be
// accessed after data from the second field has been accessed.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero.
// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines
// the PHP data type for the returned data. For information about supported data
// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return
// type is specified, a default PHP type will be returned. For information about
// default PHP types, see Default PHP Data Types. For information about
// specifying PHP data types, see How to: Specify PHP Data Types.
//
// Return Value
// The field data. You can specify the PHP data type of the returned data by
// using the $getAsType parameter. If no return data type is specified, the
// default PHP data type will be returned. For information about default PHP
// types, see Default PHP Data Types. For information about specifying PHP data
// types, see How to: Specify PHP Data Types.
PHP_FUNCTION( sqlsrv_get_field )
{
LOG_FUNCTION( "sqlsrv_get_field" );
ss_sqlsrv_stmt* stmt = NULL;
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
void* field_value = NULL;
int field_index = -1;
SQLLEN field_len = -1;
// get the statement, the field index and the optional type
PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type );
try {
// validate that the field index is within range
SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
if( field_index < 0 || field_index >= num_cols ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
core_sqlsrv_get_field( stmt, field_index, sqlsrv_php_type, false, &field_value, &field_len, false/*cache_field*/,
&sqlsrv_php_type_out TSRMLS_CC );
zval_ptr_dtor( &return_value );
*return_value_ptr = convert_to_zval( sqlsrv_php_type_out, &field_value, field_len );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_get_field: Unknown exception caught." );
}
}
// ** type functions. **
// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions
// to match that notation and return a specially encoded integer that tells us what type and size/precision
// are. For PHP types specifically we munge the type and encoding into the integer.
// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined
// below.
// takes an encoding of the stream
PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM )
{
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM );
}
// takes an encoding of the string
PHP_FUNCTION( SQLSRV_PHPTYPE_STRING )
{
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING );
}
// takes the size of the binary field
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY );
}
// takes the size of the char field
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR );
}
// takes the precision and scale of the decimal field
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL)
{
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL );
}
// takes the size of the nchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR );
}
// takes the precision and scale of the numeric field
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC)
{
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC );
}
// takes the size (in characters, not bytes) of the nvarchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR );
}
// takes the size of the varbinary field
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY );
}
// takes the size of the varchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR );
}
// mark parameters passed into sqlsrv_prepare as reference parameters so that they may be updated later in the
// script and subsequent sqlsrv_execute calls will use the new values. Marking them as references "pins" them
// to their memory location so that the buffer we give to ODBC can be relied on to be there.
void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC )
{
SQLSRV_ASSERT( stmt->params_z == NULL, "mark_params_by_reference: parameters list shouldn't be present" );
if( params_z == NULL ) {
return;
}
HashTable* params_ht = Z_ARRVAL_P( params_z );
for( zend_hash_internal_pointer_reset( params_ht );
zend_hash_has_more_elements( params_ht ) == SUCCESS;
zend_hash_move_forward( params_ht )) {
char *key = NULL;
unsigned int key_len = 0;
unsigned long index = -1;
zval** value_z = NULL;
// make sure it's an integer index
int type = zend_hash_get_current_key_ex( params_ht, &key, &key_len, &index, 0, NULL );
CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) {
throw ss::SSException();
}
core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &value_z TSRMLS_CC );
// This code turns parameters into references. Since the function declaration cannot
// pass array elements as references (without requiring & in front of each variable),
// we have to set the reference in each of the zvals ourselves. In the event of a
// parameter array (or sub array if you will) being passed in, we set the zval of the
// parameter array's first element.
// if it's a sole variable
if( Z_TYPE_PP( value_z ) != IS_ARRAY ) {
// print a warning if they didn't send it as a reference
if( !PZVAL_IS_REF( *value_z ) && Z_REFCOUNT_P( *value_z ) > 1 ) {
php_error( E_WARNING, SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF, index + 1 );
}
Z_SET_ISREF_PP( value_z ); // mark it as a reference
}
// else mark [0] as a reference
else {
zval** var = NULL;
int zr = zend_hash_index_find( Z_ARRVAL_PP( value_z ), 0, reinterpret_cast<void**>( &var ));
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) {
throw ss::SSException();
}
// print a warning if they didn't send it as a reference
if( !PZVAL_IS_REF( *var ) && Z_REFCOUNT_P( *var ) > 1 ) {
php_error( E_WARNING, SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF, index + 1 );
}
Z_SET_ISREF_PP( var ); // mark it as a reference
}
}
// save our parameters for later.
zval_add_ref( &params_z );
stmt->params_z = params_z;
}
void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC )
{
// if there's nothing to do, just return
if( stmt->params_z == NULL ) {
return;
}
try {
stmt->free_param_data( TSRMLS_C );
stmt->executed = false;
zval* params_z = stmt->params_z;
HashTable* params_ht = Z_ARRVAL_P( params_z );
for( zend_hash_internal_pointer_reset( params_ht );
zend_hash_has_more_elements( params_ht ) == SUCCESS;
zend_hash_move_forward( params_ht )) {
char *key = NULL;
unsigned int key_len = 0;
unsigned long index = -1;
zval** param_z = NULL;
zval* value_z = NULL;
int direction = SQL_PARAM_INPUT;
SQLSRV_ENCODING encoding = stmt->encoding();
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
SQLSMALLINT decimal_digits = 0;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
// make sure it's an integer index
int type = zend_hash_get_current_key_ex( params_ht, &key, &key_len, &index, 0, NULL );
CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) {
throw ss::SSException();
}
core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &param_z TSRMLS_CC );
// if it's a parameter array
if( Z_TYPE_PP( param_z ) == IS_ARRAY ) {
zval** var = NULL;
int zr = zend_hash_index_find( Z_ARRVAL_PP( param_z ), 0, reinterpret_cast<void**>( &var ));
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) {
zval_ptr_dtor( &params_z );
throw ss::SSException();
}
// parse the parameter array that the user gave
parse_param_array( stmt, *param_z, index, direction, php_out_type, encoding, sql_type, column_size,
decimal_digits TSRMLS_CC );
value_z = *var;
}
else {
value_z = *param_z;
}
// bind the parameter
core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, decimal_digits
TSRMLS_CC );
}
}
catch( core::CoreException& ) {
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
zval_ptr_dtor( &stmt->params_z );
stmt->params_z = NULL;
throw;
}
}
// sqlsrv_cancel( resource $stmt )
//
// Cancels a statement. This means that any pending results for the statement
// are discarded. After this function is called, the statement can be
// re-executed if it was prepared with sqlsrv_prepare. Calling this function is
// not necessary if all the results associated with the statement have been
// consumed.
//
// Parameters
// $stmt: The statement to be canceled.
//
// Return Value
// A Boolean value: true if the operation was successful. Otherwise, false.
PHP_FUNCTION( sqlsrv_cancel )
{
LOG_FUNCTION( "sqlsrv_cancel" );
ss_sqlsrv_stmt* stmt = NULL;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// close the stream to release the resource
close_active_stream( stmt TSRMLS_CC );
SQLRETURN r = SQLCancel( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw ss::SSException();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_cancel: Unknown exception caught." );
}
}
void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC )
{
LOG_FUNCTION( "sqlsrv_stmt_dtor" );
// get the structure
ss_sqlsrv_stmt *stmt = static_cast<ss_sqlsrv_stmt*>( rsrc->ptr );
if( stmt->conn ) {
int zr = zend_hash_index_del( static_cast<ss_sqlsrv_conn*>( stmt->conn )->stmts, stmt->conn_index );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to remove statement reference from the connection" );
}
}
stmt->~ss_sqlsrv_stmt();
sqlsrv_free( stmt );
rsrc->ptr = NULL;
}
// sqlsrv_free_stmt( resource $stmt )
//
// Frees all resources associated with the specified statement. The statement
// cannot be used again after this function has been called.
//
// Parameters
// $stmt: The statement to be closed.
//
// Return Value
// The Boolean value true unless the function is called with an invalid
// parameter. If the function is called with an invalid parameter, false is
// returned.
//
// Null is a valid parameter for this function. This allows the function to be
// called multiple times in a script. For example, if you free a statement in an
// error condition and free it again at the end of the script, the second call
// to sqlsrv_free_stmt will return true because the first call to
// sqlsrv_free_stmt (in the error condition) sets the statement resource to
// null.
PHP_FUNCTION( sqlsrv_free_stmt )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_free_stmt" );
zval* stmt_r = NULL;
ss_sqlsrv_stmt* stmt = NULL;
sqlsrv_context_auto_ptr error_ctx;
// we do this manually instead of with PROCESS_PARAMS because we return TRUE even if there is a parameter error.
full_mem_check(MEMCHECK_SILENT);
reset_errors( TSRMLS_C );
try {
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
// take only the statement resource
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) {
// Check if it was a zval
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
if( Z_TYPE_P( stmt_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
// verify the resource so we know we're deleting a statement
stmt = static_cast<ss_sqlsrv_stmt*>( zend_fetch_resource( &stmt_r TSRMLS_CC, -1, ss_sqlsrv_stmt::resource_name, NULL,
1, ss_sqlsrv_stmt::descriptor ));
if( stmt == NULL ) {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
// delete the resource from Zend's master list, which will trigger the statement's destructor
int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_r ));
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RESVAL_P( stmt_r ));
}
ZVAL_NULL( stmt_r );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_free_stmt: Unknown exception caught." );
}
}
void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
{
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) {
throw ss::SSException();
}
const char* scroll_type = Z_STRVAL_P( value_z );
unsigned int cursor_type = -1;
// find which cursor type they would like and set the ODBC statement attribute as such
if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) {
cursor_type = SQL_CURSOR_STATIC;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) {
cursor_type = SQL_CURSOR_DYNAMIC;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) {
cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) {
cursor_type = SQL_CURSOR_FORWARD_ONLY;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) {
cursor_type = SQLSRV_CURSOR_BUFFERED;
}
else {
THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
}
core_sqlsrv_set_scrollable( stmt, cursor_type TSRMLS_CC );
}
namespace {
zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len )
{
zval* out_zval = NULL;
switch( sqlsrv_php_type ) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
ALLOC_INIT_ZVAL( out_zval );
if( *in_val == NULL ) {
ZVAL_NULL( out_zval );
}
else {
if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) {
ZVAL_LONG( out_zval, **( reinterpret_cast<long**>( in_val )));
}
else {
ZVAL_DOUBLE( out_zval, **( reinterpret_cast<double**>( in_val )));
}
}
if( *in_val ) {
sqlsrv_free( *in_val );
}
break;
}
case SQLSRV_PHPTYPE_STRING:
{
ALLOC_INIT_ZVAL( out_zval );
if( *in_val == NULL ) {
ZVAL_NULL( out_zval );
}
else {
ZVAL_STRINGL( out_zval, reinterpret_cast<char*>( *in_val ), field_len, 0 /*duplicate*/ );
}
break;
}
case SQLSRV_PHPTYPE_STREAM:
case SQLSRV_PHPTYPE_DATETIME:
out_zval = ( reinterpret_cast<zval*>( *in_val ));
break;
case SQLSRV_PHPTYPE_NULL:
ALLOC_INIT_ZVAL( out_zval );
ZVAL_NULL( out_zval );
break;
default:
DIE( "Unknown php type" );
break;
}
return out_zval;
}
// put in the column size and scale/decimal digits of the sql server type
// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, __out SQLUINTEGER* column_size,
__out SQLSMALLINT* decimal_digits )
{
*decimal_digits = 0;
switch( sqlsrv_type.typeinfo.type ) {
case SQL_BIGINT:
*column_size = 19;
break;
case SQL_BIT:
*column_size = 1;
break;
case SQL_INTEGER:
*column_size = 10;
break;
case SQL_SMALLINT:
*column_size = 5;
break;
case SQL_TINYINT:
*column_size = 3;
break;
case SQL_GUID:
*column_size = 36;
break;
case SQL_FLOAT:
*column_size = 53;
break;
case SQL_REAL:
*column_size = 24;
break;
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
*column_size = LONG_MAX;
break;
case SQL_WLONGVARCHAR:
*column_size = LONG_MAX >> 1;
break;
case SQL_SS_XML:
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
case SQL_BINARY:
case SQL_CHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*column_size = SQL_SS_LENGTH_UNLIMITED;
}
else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
}
if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
case SQL_DECIMAL:
case SQL_NUMERIC:
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
// if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision
if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
// this can represent one of three data types: smalldatetime, datetime, and datetime2
// we present the largest for the version and let SQL Server downsize it
case SQL_TYPE_TIMESTAMP:
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
break;
case SQL_SS_TIMESTAMPOFFSET:
*column_size = 34;
*decimal_digits = 7;
break;
case SQL_TYPE_DATE:
*column_size = 10;
*decimal_digits = 0;
break;
case SQL_SS_TIME2:
*column_size = 16;
*decimal_digits = 7;
break;
default:
// an invalid sql type should have already been dealt with, so we assert here.
DIE( "Trying to determine column size for an invalid type. Type should have already been verified." );
return false;
}
return true;
}
// given a SQL Server type, return a sqlsrv php type
sqlsrv_phptype determine_sqlsrv_php_type( ss_sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string )
{
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
switch( sql_type ) {
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_WCHAR:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
break;
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
break;
case SQL_FLOAT:
case SQL_REAL:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
{
ss_sqlsrv_conn* c = static_cast<ss_sqlsrv_conn*>( stmt->conn );
if( c->date_as_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
}
default:
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
break;
}
// if an encoding hasn't been set for the statement, then use the connection's encoding
if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding();
}
return sqlsrv_phptype;
}
// determine if a query returned any rows of data. It does this by actually fetching the first row
// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful.
// The return value simply states whether or not if an error occurred during the determination.
// (All errors are posted here before returning.)
void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
if( stmt->fetch_called ) {
return;
}
// default condition
stmt->has_rows = false;
// if there are no columns then there are no rows
if( core::SQLNumResultCols( stmt TSRMLS_CC ) == 0 ) {
return;
}
// if the statement is scrollable, our work is easier though less performant. We simply
// fetch the first row, and then roll the cursor back to be prior to the first row
if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) {
r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 TSRMLS_CC );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt );
// restore the cursor to its original position.
r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 TSRMLS_CC );
SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning "
"of the result set." );
}
}
else {
// otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this
// flag and simply skips the first fetch, knowing it was already done. It records its own
// flags to know if it should fetch on subsequent calls.
r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt );
return;
}
}
}
void fetch_fields_common( __inout ss_sqlsrv_stmt* stmt, int fetch_type, __out zval* return_value, bool allow_empty_field_names
TSRMLS_DC )
{
void* field_value = NULL;
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
zval_auto_ptr fields;
// make sure that the fetch type is legal
CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func() ) {
throw ss::SSException();
}
// get the numer of columns in the result set
SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
// if this is the first fetch in a new result set, then get the field names and
// store them off for successive fetches.
if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) {
SQLSMALLINT field_name_len;
char field_name_temp[ SS_MAXCOLNAMELEN+1 ];
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names;
field_names = static_cast<sqlsrv_fetch_field_name*>( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name )));
for( int i = 0; i < num_cols; ++i ) {
core::SQLColAttribute( stmt, i + 1, SQL_DESC_NAME, field_name_temp, SS_MAXCOLNAMELEN+1, &field_name_len, NULL
TSRMLS_CC );
field_names[ i ].name = static_cast<char*>( sqlsrv_malloc( field_name_len, sizeof( char ), 1 ));
memcpy( (void*) field_names[ i ].name, field_name_temp, field_name_len );
field_names[ i ].name[ field_name_len ] = '\0'; // null terminate the field name since SQLColAttribute doesn't.
field_names[ i ].len = field_name_len + 1;
}
stmt->fetch_field_names = field_names;
stmt->fetch_fields_count = num_cols;
field_names.transferred();
}
MAKE_STD_ZVAL( fields );
int zr = array_init( fields );
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
throw ss::SSException();
}
// get the fields
for( int i=0; i< num_cols; ++i ) {
// this zval_auto_ptr is never transferred because we rely on its destructor to decrement the reference count
// we increment its reference count within each fetch type (below)
zval_auto_ptr field;
SQLLEN field_len = -1;
core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/,
&field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC );
field = convert_to_zval( sqlsrv_php_type_out, &field_value, field_len );
if( fetch_type & SQLSRV_FETCH_NUMERIC ) {
zr = add_next_index_zval( fields, field );
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
throw ss::SSException();
}
zval_add_ref( &field );
}
if( fetch_type & SQLSRV_FETCH_ASSOC ) {
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[ i ].len == 1 && !allow_empty_field_names ), stmt,
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY ) {
throw ss::SSException();
}
if( stmt->fetch_field_names[ i ].len > 1 || allow_empty_field_names ) {
zr = add_assoc_zval( fields, stmt->fetch_field_names[ i ].name, field );
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
throw ss::SSException();
}
zval_add_ref( &field );
}
}
} //for loop
*return_value = *fields;
ZVAL_NULL( fields );
zval_ptr_dtor( &fields );
fields.transferred();
}
void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigned long index, __out int& direction,
__out SQLSRV_PHPTYPE& php_out_type, __out SQLSRV_ENCODING& encoding, __out SQLSMALLINT& sql_type,
__out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC )
{
zval** var_or_val;
zval** temp;
HashTable* param_ht = Z_ARRVAL_P( param_array );
sqlsrv_sqltype sqlsrv_sql_type;
try {
bool php_type_param_was_null = true;
bool sql_type_param_was_null = true;
php_out_type = SQLSRV_PHPTYPE_INVALID;
encoding = SQLSRV_ENCODING_INVALID;
// handle the array parameters that contain the value/var, direction, php_type, sql_type
zend_hash_internal_pointer_reset( param_ht );
if( zend_hash_has_more_elements( param_ht ) == FAILURE ||
zend_hash_get_current_data( param_ht, (void**) &var_or_val ) == FAILURE ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 );
}
// if the direction is included, then use what they gave, otherwise INPUT is assumed
if( zend_hash_move_forward( param_ht ) == SUCCESS && zend_hash_get_current_data( param_ht, (void**) &temp ) == SUCCESS &&
Z_TYPE_PP( temp ) != IS_NULL ) {
CHECK_CUSTOM_ERROR( Z_TYPE_PP( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) {
throw ss::SSException();
}
direction = Z_LVAL_PP( temp );
CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) {
throw ss::SSException();
}
}
else {
direction = SQL_PARAM_INPUT;
}
// extract the php type and encoding from the 3rd parameter
if( zend_hash_move_forward( param_ht ) == SUCCESS && zend_hash_get_current_data( param_ht, (void**) &temp ) == SUCCESS &&
Z_TYPE_PP( temp ) != IS_NULL ) {
php_type_param_was_null = false;
sqlsrv_phptype sqlsrv_phptype;
CHECK_CUSTOM_ERROR( Z_TYPE_PP( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) {
throw ss::SSException();
}
sqlsrv_phptype.value = Z_LVAL_PP( temp );
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
index + 1 ) {
throw ss::SSException();
}
php_out_type = static_cast<SQLSRV_PHPTYPE>( sqlsrv_phptype.typeinfo.type );
encoding = (SQLSRV_ENCODING) sqlsrv_phptype.typeinfo.encoding;
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
// by the connection
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
}
// set default for php type and encoding if not supplied
else {
php_type_param_was_null = true;
php_out_type = zend_to_sqlsrv_phptype[ Z_TYPE_PP( var_or_val ) ];
encoding = stmt->encoding();
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
}
// get the server type, column size/precision and the decimal digits if provided
if( zend_hash_move_forward( param_ht ) == SUCCESS && zend_hash_get_current_data( param_ht, (void**) &temp ) == SUCCESS &&
Z_TYPE_PP( temp ) != IS_NULL ) {
sql_type_param_was_null = false;
CHECK_CUSTOM_ERROR( Z_TYPE_PP( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) {
throw ss::SSException();
}
sqlsrv_sql_type.value = Z_LVAL_PP( temp );
// since the user supplied this type, make sure it's valid
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
index + 1 ) {
throw ss::SSException();
}
bool size_okay = determine_column_size_or_precision( stmt, sqlsrv_sql_type, &column_size, &decimal_digits );
CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) {
throw ss::SSException();
}
sql_type = sqlsrv_sql_type.typeinfo.type;
}
// else the sql type and size are uknown, so tell the core layer to use its defaults
else {
sql_type_param_was_null = true;
sql_type = SQL_UNKNOWN_TYPE;
column_size = SQLSRV_UNKNOWN_SIZE;
decimal_digits = 0;
}
// if the user for some reason provides an output parameter with a null phptype and a specified
// sql server type, infer the php type from the sql server type.
if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) {
int encoding;
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, column_size, true );
// we DIE here since everything should have been validated already and to return the user an error
// for our own logic error would be confusing/misleading.
SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) "
"validated sql type and column_size" );
php_out_type = static_cast<SQLSRV_PHPTYPE>( sqlsrv_phptype.typeinfo.type );
encoding = sqlsrv_phptype.typeinfo.encoding;
}
// verify that the parameter is a valid output param type
if( direction == SQL_PARAM_OUTPUT ) {
switch( php_out_type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
THROW_CORE_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE );
break;
default:
break;
}
}
}
catch( core::CoreException& ) {
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
throw;
}
}
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type )
{
switch( type.typeinfo.type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
{
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
return true;
}
break;
}
}
return false;
}
// return if the type is a valid sql server type not including
// size, precision or scale. Use determine_precision_and_scale for that.
bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type )
{
switch( sql_type.typeinfo.type ) {
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_GUID:
case SQL_FLOAT:
case SQL_REAL:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
case SQL_BINARY:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
break;
default:
return false;
}
return true;
}
// verify an encoding given to type_and_encoding by looking through the list
// of standard encodings created at module initialization time
bool verify_and_set_encoding( const char* encoding_string, __out sqlsrv_phptype& phptype_encoding TSRMLS_DC )
{
for( zend_hash_internal_pointer_reset( g_ss_encodings_ht );
zend_hash_has_more_elements( g_ss_encodings_ht ) == SUCCESS;
zend_hash_move_forward( g_ss_encodings_ht ) ) {
sqlsrv_encoding* encoding;
int zr = zend_hash_get_current_data( g_ss_encodings_ht, (void**) &encoding );
if( zr == FAILURE ) {
DIE( "Fatal: Error retrieving encoding from encoding hash table." );
}
if( !stricmp( encoding_string, encoding->iana )) {
phptype_encoding.typeinfo.encoding = encoding->code_page;
return true;
}
}
return false;
}
// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size
// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h).
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
char* size_p = NULL;
int size_len = 0;
long size = 0;
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) {
return;
}
if( !strnicmp( "max", size_p, sizeof( "max" ) / sizeof(char)) ) {
size = SQLSRV_SIZE_MAX_TYPE;
}
else {
_set_errno( 0 ); // reset errno for atol
size = atol( size_p );
if( errno != 0 ) {
size = SQLSRV_INVALID_SIZE;
}
}
int max_size = SQL_SERVER_MAX_FIELD_SIZE;
// size is actually the number of characters, not the number of bytes, so if they ask for a
// 2 byte per character type, then we half the maximum size allowed.
if( type == SQL_WVARCHAR || type == SQL_WCHAR ) {
max_size >>= 1;
}
if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) {
LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size );
size = SQLSRV_INVALID_SIZE;
}
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = type;
sql_type.typeinfo.size = size;
sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE;
ZVAL_LONG( return_value, sql_type.value );
}
// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the
// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h)
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
long prec = SQLSRV_INVALID_PRECISION;
long scale = SQLSRV_INVALID_SCALE;
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) {
return;
}
if( prec > SQL_SERVER_MAX_PRECISION ) {
LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" );
prec = SQLSRV_INVALID_PRECISION;
}
if( prec < 0 ) {
LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" );
prec = SQLSRV_INVALID_PRECISION;
}
if( scale > prec ) {
LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" );
scale = SQLSRV_INVALID_SCALE;
}
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = type;
sql_type.typeinfo.size = prec;
sql_type.typeinfo.scale = scale;
ZVAL_LONG( return_value, sql_type.value );
}
// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters.
// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h)
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." );
char* encoding_param;
int encoding_param_len = 0;
// set the default encoding values to invalid so that
// if the encoding isn't validated, it will return the invalid setting.
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = type;
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) {
ZVAL_LONG( return_value, sqlsrv_php_type.value );
}
if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type TSRMLS_CC )) {
LOG( SEV_ERROR, "Invalid encoding for php type." );
}
ZVAL_LONG( return_value, sqlsrv_php_type.value );
}
}

View file

@ -1,83 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: template.rc
//
// Contents: Version resource
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#ifdef APSTUDIO_INVOKED
# error dont edit with MSVC
#endif
#include "winresrc.h"
#include "main/php_version.h"
#include "version.h"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifndef THANKS_GUYS
# define THANKS_GUYS ""
#endif
#ifdef WANT_LOGO
0 ICON win32\build\php.ico
#endif
#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build
#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel
#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build)
#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel)
//Version
VS_VERSION_INFO VERSIONINFO
FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION
PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. © 1997-2009 The PHP Group. All rights reserved.\0"
VALUE "CompanyName", "Microsoft Corp.\0"
VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)\0"
VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION)
VALUE "InternalName", FILE_NAME "\0"
VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0"
VALUE "OriginalFilename", FILE_NAME "\0"
VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0"
VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD)
VALUE "URL", "http://www.microsoft.com\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#ifdef MC_INCLUDE
#include MC_INCLUDE
#endif

View file

@ -1,958 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: util.cpp
//
// Contents: Utility functions used by both connection or statement functions
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
#include <windows.h>
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_UTIL;
// buffer used to hold a formatted log message prior to actually logging it.
const int LOG_MSG_SIZE = 2048;
char log_msg[ LOG_MSG_SIZE ];
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// *** internal functions ***
void copy_error_to_zval( zval** error_z, sqlsrv_error_const* error, zval** reported_chain, zval** ignored_chain,
bool warning TSRMLS_DC );
bool ignore_warning( char* sql_state, int native_code TSRMLS_DC );
bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zval** ignored_chain, logging_severity log_severity,
unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC );
int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC );
bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC );
}
// List of all error messages
ss_error SS_ERRORS[] = {
{
SS_SQLSRV_ERROR_INVALID_OPTION,
{ IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true }
},
// no equivalent to error 2 in 2.0
// error 3 is superceded by -16
// these two share the same code since they are basically the same error.
{
SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be"
" escaped with another right brace (}}).", -4, false }
},
{
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true }
},
{
SQLSRV_ERROR_NO_DATA,
{ IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true }
},
{
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
{ IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using "
"streams.", -6, false}
},
{
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
{ IMSSP, (SQLCHAR*)"An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be "
"specified as output parameters.", -7, false }
},
{
SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY,
{ IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false }
},
{
SS_SQLSRV_ERROR_VAR_REQUIRED,
{ IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true }
},
{
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE,
{ IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false }
},
{
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
{ IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false }
},
{
SS_SQLSRV_ERROR_ALREADY_IN_TXN,
{ IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either "
"sqlsrv_commit or sqlsrv_rollback.", -12, false }
},
{
SS_SQLSRV_ERROR_NOT_IN_TXN,
{ IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or "
"sqlsrv_rollback.", -13, false }
},
{
SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER,
{ IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true }
},
{
SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION,
{ IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and "
"SQLSRV_PARAM_INOUT are valid values.", -15, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
{ IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
{ IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true }
},
{
SQLSRV_ERROR_FETCH_NOT_CALLED,
{ IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false }
},
{
SQLSRV_ERROR_FIELD_INDEX_ERROR,
{ IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. "
"The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less "
"than the index of a field that has already been retrieved (%2!d!).", -19, true }
},
{
SQLSRV_ERROR_DATETIME_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false }
},
// no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped
{
SQLSRV_ERROR_FETCH_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more rows in the active result set. Since this result set is not scrollable, no more "
"data may be retrieved.", -22, false }
},
{
SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED,
{ IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false }
},
{
SQLSRV_ERROR_ZEND_HASH,
{ IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false }
},
{
SQLSRV_ERROR_ZEND_STREAM,
{ IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false }
},
{
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false }
},
{
SQLSRV_ERROR_STREAM_CREATE,
{ IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false }
},
{
SQLSRV_ERROR_NO_FIELDS,
{ IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false }
},
{
SS_SQLSRV_ERROR_ZEND_BAD_CLASS,
{ IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true }
},
{
SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED,
{ IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true }
},
{
SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION,
{ IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_KEY,
{ IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true }
},
// these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1
{
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true }
},
{
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
{ IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant."
" For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. "
"If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true }
},
{
SQLSRV_ERROR_INVALID_TYPE,
{ IMSSP, (SQLCHAR*)"Invalid type", -35, false }
},
// 36-38 have no equivalent 2.0 errors
{
SS_SQLSRV_ERROR_REGISTER_RESOURCE,
{ IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true }
},
{
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true }
},
{
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true }
},
{
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true }
},
{
SQLSRV_ERROR_MARS_OFF,
{ IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. "
"To make the connection available for other queries, either fetch all results or cancel or free the statement. "
"For more information, see the product documentation about the MultipleActiveResultSets connection option.", -44, false }
},
{
SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
{ IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of "
"key/value pairs.", -45, false }
},
{
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true }
},
{
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true }
},
{
SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING,
{ IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true }
},
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 for SQL Server. "
"Access the following URL to download the ODBC Driver 11 for SQL Server for %1!s!: "
"http://go.microsoft.com/fwlink/?LinkId=163712", -49, true }
},
{
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false }
},
{
SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false }
},
// new error for 2.0, used here since 1.1 didn't have a -52
{
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true }
},
{
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE,
{ IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. "
"Please use one of the SQLSRV_SCROLL constants.", -53, false }
},
{
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', "
"'keyset', 'forward', or 'buffered'.", -54, false }
},
{
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
{ IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true }
},
{
SS_SQLSRV_ERROR_PARAM_INVALID_INDEX,
{ IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
{ IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true }
},
{
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true }
},
{
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
{ IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false }
},
// internal warning definitions
{
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
{ SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false }
},
// terminate the list of errors/warnings
{ -1, {} }
};
sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) {
sqlsrv_error_const *error_message = NULL;
int zr = zend_hash_index_find( g_ss_errors_ht, sqlsrv_error_code, reinterpret_cast<void**>( &error_message ));
if( zr == FAILURE ) {
DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code );
}
SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null");
return error_message;
}
// Formats an error message and finally writes it to the php log.
void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args )
{
if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) {
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args );
// if an error occurs for FormatMessage, we just output an internal error occurred.
if( rc == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg ));
std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg );
}
php_log_err( log_msg TSRMLS_CC );
}
}
bool ss_error_handler(sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args )
{
logging_severity severity = SEV_ERROR;
if( warning && !SQLSRV_G( warnings_return_as_errors )) {
severity = SEV_WARNING;
}
return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning,
print_args TSRMLS_CC );
}
// sqlsrv_errors( [int $errorsAndOrWarnings] )
//
// Returns extended error and/or warning information about the last sqlsrv
// operation performed.
//
// The sqlsrv_errors function can return error and/or warning information by
// calling it with one of the following parameter values below.
//
// Parameters
//
// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can
// take one of the values listed:
//
// SQLSRV_ERR_ALL
// Errors and warnings generated on the last sqlsrv function call are returned.
// SQLSRV_ERR_ERRORS
// Errors generated on the last sqlsrv function call are returned.
// SQLSRV_ERR_WARNINGS
// Warnings generated on the last sqlsrv function call are returned.
//
// If no parameter value is supplied, SQLSRV_ERR_ALL is the default
//
// Return Value
// An array of arrays, or null. An example of an error returned:
// Array
// (
// [0] => Array
// (
// [0] => HYT00
// [SQLSTATE] => HYT00
// [1] => 0
// [code] => 0
// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired
// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired
// )
// )
PHP_FUNCTION( sqlsrv_errors )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
long flags = SQLSRV_ERR_ALL;
full_mem_check(MEMCHECK_SILENT);
LOG_FUNCTION( "sqlsrv_errors" );
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) {
LOG(SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ );
RETURN_FALSE;
}
if( flags == SQLSRV_ERR_ALL ) {
int result;
zval_auto_ptr both_z;
MAKE_STD_ZVAL( both_z );
result = array_init( both_z );
if( result == FAILURE ) {
RETURN_FALSE;
}
if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( errors ) TSRMLS_CC )) {
RETURN_FALSE;
}
if( Z_TYPE_P( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( warnings ) TSRMLS_CC )) {
RETURN_FALSE;
}
if( zend_hash_num_elements( Z_ARRVAL_P( both_z )) == 0 ) {
RETURN_NULL();
}
zval_ptr_dtor( &return_value );
*return_value_ptr = both_z;
both_z.transferred();
}
else if( flags == SQLSRV_ERR_WARNINGS ) {
zval_ptr_dtor( &return_value );
*return_value_ptr = SQLSRV_G( warnings );
zval_add_ref( &SQLSRV_G( warnings ));
}
else {
zval_ptr_dtor( &return_value );
*return_value_ptr = SQLSRV_G( errors );
zval_add_ref( &SQLSRV_G( errors ));
}
}
// sqlsrv_configure( string $setting, mixed $value )
//
// Changes the settings for error handling and logging options.
//
// Parameters
// $setting: The name of the setting to be configured. The possible implemented values are
// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity".
//
// $value: The value to be applied to the setting specified in the $setting
// parameter. See MSDN or the MINIT function for possible values.
//
// Return Value
// If sqlsrv_configure is called with an unsupported setting or value, the
// function returns false. Otherwise, the function returns true.
PHP_FUNCTION( sqlsrv_configure )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
LOG_FUNCTION( "sqlsrv_configure" );
char* option;
int option_len;
zval* value_z;
sqlsrv_context_auto_ptr error_ctx;
RETVAL_FALSE;
reset_errors( TSRMLS_C );
try {
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
// WarningsReturnAsErrors
if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) {
SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false;
LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off");
RETURN_TRUE;
}
// LogSeverity
else if( !stricmp( option, INI_LOG_SEVERITY )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
long severity_mask = Z_LVAL_P( value_z );
// make sure they can't use 0 to shut off the masking in the severity
if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) {
RETURN_FALSE;
}
SQLSRV_G( log_severity ) = static_cast<logging_severity>( severity_mask );
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity ));
RETURN_TRUE;
}
// LogSubsystems
else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
long subsystem_mask = Z_LVAL_P( value_z );
if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) {
RETURN_FALSE;
}
SQLSRV_G( log_subsystems ) = static_cast<logging_subsystems>( subsystem_mask );
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems ));
RETURN_TRUE;
}
else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) {
throw ss::SSException();
}
long buffered_query_limit = Z_LVAL_P( value_z );
CHECK_CUSTOM_ERROR( buffered_query_limit < 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) {
throw ss::SSException();
}
SQLSRV_G( buffered_query_limit ) = buffered_query_limit;
LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit ));
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_configure: Unknown exception caught." );
}
}
// sqlsrv_get_config( string $setting )
//
// Returns the current value of the specified configuration setting.
//
// Parameters
// $setting: The configuration setting for which the value is returned. For a
// list of configurable settings, see sqlsrv_configure.
//
// Return Value
// The value of the setting specified by the $setting parameter. If an invalid
// setting is specified, false is returned and an error is added to the error
// collection. Because false is a valid value for WarningsReturnAsErrors, to
// really determine if an error occurred, call sqlsrv_errors.
PHP_FUNCTION( sqlsrv_get_config )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
char* option = NULL;
int option_len;
sqlsrv_context_auto_ptr error_ctx;
LOG_FUNCTION( "sqlsrv_get_config" );
reset_errors( TSRMLS_C );
try {
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) {
ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors ));
return;
}
else if( !stricmp( option, INI_LOG_SEVERITY )) {
ZVAL_LONG( return_value, SQLSRV_G( log_severity ));
return;
}
else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) {
ZVAL_LONG( return_value, SQLSRV_G( log_subsystems ));
return;
}
else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) {
ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit ));
return;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_get_config: Unknown exception caught." );
}
}
namespace {
void copy_error_to_zval( zval** error_z, sqlsrv_error_const* error, zval** reported_chain, zval** ignored_chain,
bool warning TSRMLS_DC )
{
MAKE_STD_ZVAL( *error_z );
if( array_init( *error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
// sqlstate
zval_auto_ptr temp;
MAKE_STD_ZVAL( temp );
ZVAL_STRINGL( temp, reinterpret_cast<char*>( error->sqlstate ), SQL_SQLSTATE_SIZE, 1 );
zval_add_ref( &temp );
if( add_next_index_zval( *error_z, temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
if( add_assoc_zval( *error_z, "SQLSTATE", temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
temp.transferred();
// native_code
if( add_next_index_long( *error_z, error->native_code ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
if( add_assoc_long( *error_z, "code", error->native_code ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
// native_message
MAKE_STD_ZVAL( temp );
ZVAL_STRING( temp, reinterpret_cast<char*>( error->native_message), 1 );
zval_add_ref( &temp );
if( add_next_index_zval( *error_z, temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
if( add_assoc_zval( *error_z, "message", temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
temp.transferred();
// If it is an error or if warning_return_as_errors is true than
// add the error or warning to the reported_chain.
if( !warning || SQLSRV_G( warnings_return_as_errors ) )
{
// if the warning is part of the ignored warning list than
// add to the ignored chain if the ignored chain is not null.
if( warning && ignore_warning( reinterpret_cast<char*>( error->sqlstate ), error->native_code TSRMLS_CC ) &&
ignored_chain != NULL ) {
if( add_next_index_zval( *ignored_chain, *error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
else {
// It is either an error or a warning which should not be ignored.
if( add_next_index_zval( *reported_chain, *error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
}
else
{
// It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list
if( ignored_chain != NULL ) {
if( add_next_index_zval( *ignored_chain, *error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
}
}
bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zval** ignored_chain, logging_severity log_severity,
unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC )
{
bool result = true;
bool errors_ignored = false;
int prev_reported_cnt = 0;
bool reported_chain_was_null = false;
bool ignored_chain_was_null = false;
int zr = SUCCESS;
zval* error_z = NULL;
sqlsrv_error_auto_ptr error;
// array of reported errors
if( Z_TYPE_P( *reported_chain ) == IS_NULL ) {
reported_chain_was_null = true;
zr = array_init( *reported_chain );
if( zr == FAILURE ) {
DIE( "Fatal error in handle_errors_and_warnings" );
}
}
else {
prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_PP( reported_chain ));
}
// array of ignored errors
if( ignored_chain != NULL ) {
if( Z_TYPE_P( *ignored_chain ) == IS_NULL ) {
ignored_chain_was_null = true;
zr = array_init( *ignored_chain );
if( zr == FAILURE ) {
DIE( "Fatal error in handle_errors_and_warnings" );
}
}
}
if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity TSRMLS_CC, print_args );
copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC );
}
SQLSMALLINT record_number = 0;
do {
result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity TSRMLS_CC );
if( result ) {
copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC );
}
} while( result );
// If it were a warning, we report that warnings where ignored except if warnings_return_as_errors
// was true and we added some warnings to the reported_chain.
if( warning ) {
errors_ignored = true;
if( SQLSRV_G( warnings_return_as_errors ) ) {
if( zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) > prev_reported_cnt ) {
// We actually added some errors
errors_ignored = false;
}
}
}
// if the error array came in as NULL and didn't have anything added to it, return it as NULL
if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) == 0 ) {
zend_hash_destroy( Z_ARRVAL_PP( reported_chain ));
FREE_HASHTABLE( Z_ARRVAL_PP( reported_chain ));
ZVAL_NULL( *reported_chain );
}
if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )) == 0 ) {
zend_hash_destroy( Z_ARRVAL_PP( ignored_chain ));
FREE_HASHTABLE( Z_ARRVAL_PP( ignored_chain ));
ZVAL_NULL( *ignored_chain );
}
// If it was an error instead of a warning than we always return errors_ignored = false.
return errors_ignored;
}
// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true
// see RINIT in init.cpp for information about which errors are ignored.
bool ignore_warning( char* sql_state, int native_code TSRMLS_DC )
{
for( zend_hash_internal_pointer_reset( g_ss_warnings_to_ignore_ht );
zend_hash_has_more_elements( g_ss_warnings_to_ignore_ht ) == SUCCESS;
zend_hash_move_forward( g_ss_warnings_to_ignore_ht ) ) {
void* error_v = NULL;
if( zend_hash_get_current_data( g_ss_warnings_to_ignore_ht, (void**) &error_v ) == FAILURE ) {
return false;
}
sqlsrv_error* error = static_cast<sqlsrv_error*>( error_v );
if( !strncmp( reinterpret_cast<char*>( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) &&
( error->native_code == native_code || error->native_code == -1 )) {
return true;
}
}
return false;
}
int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC )
{
#if defined(ZTS)
SQLSRV_UNUSED( tsrm_ls );
#endif
zval_ptr_dtor( reinterpret_cast<zval**>( &dest ));
return ZEND_HASH_APPLY_REMOVE;
}
// sqlsrv_merge_zend_hash
// merge a source hash into a dest hash table and return any errors.
bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC )
{
#if defined(ZTS)
SQLSRV_UNUSED( tsrm_ls );
#endif
if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" );
if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" );
if( Z_TYPE_P( src_z ) == IS_NULL ) {
return true;
}
HashTable* src_ht = Z_ARRVAL_P( src_z );
int result = SUCCESS;
for( zend_hash_internal_pointer_reset( src_ht );
zend_hash_has_more_elements( src_ht ) == SUCCESS;
zend_hash_move_forward( src_ht ) ) {
void* value_v;
zval* value_z;
result = zend_hash_get_current_data( src_ht, (void**) &value_v );
if( result == FAILURE ) {
zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC );
return false;
}
value_z = *(static_cast<zval**>( value_v ));
result = add_next_index_zval( dest_z, value_z );
if( result == FAILURE ) {
zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC );
return false;
}
zval_add_ref( &value_z );
}
return true;
}
} // namespace

View file

@ -1,27 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: version.h
// Contents: Version number constants
//
// Microsoft Drivers 3.2 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.
//---------------------------------------------------------------------------------------------------------------------------------
#define VER_FILEVERSION_STR "3.2.1.0"
#define _FILEVERSION 3,2,1,0
#define SQLVERSION_MAJOR 3
#define SQLVERSION_MINOR 2
#define SQLVERSION_MMDD 1
#define SQLVERSION_REVISION 0