php-sqlsrv/conn.cpp
icosahedron ed20a39316 Initial commit of the SQL Server 2005 Driver for PHP 1.0
Please see the enclosed README.TXT for more information.

This software is licensed under the Microsoft Public License.  The license is available for viewing at http://codeplex.com/SQL2K5PHP/license
2008-07-28 21:49:47 +00:00

1263 lines
52 KiB
C++

//----------------------------------------------------------------------------------------------------------------------------------
// File: conn.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: Routines that use connection handles
//
// Comments:
//
// License: This software is released under the Microsoft Public License. A copy of the license agreement
// may be found online at http://www.codeplex.com/SQL2K5PHP/license.
//----------------------------------------------------------------------------------------------------------------------------------
#include "php_sqlsrv.h"
#include <psapi.h>
#include <windows.h>
#include <winver.h>
#include <string>
#include <sstream>
// *** internal variables and constants ***
namespace {
// *** internal constants ***
// current subsytem. defined for the CHECK_* error macros
int current_log_subsystem = LOG_CONN;
// an arbitrary figure that should be large enough for most connection strings.
const int DEFAULT_CONN_STR_LEN = 2048;
// the largest packet size that can be sent with the default options that we use
// without causing a warning to happen at login time. If the warning did occur,
// it would cause an error when 'WarningsReturnAsErrors' is set to true.
const int DEFAULT_PACKET_SIZE = 32564;
// PHP streams generally return no more than 8k.
const int PHP_STREAM_BUFFER_SIZE = 8192;
// connection timeout string
const char QUERY_TIMEOUT[] = "QueryTimeout";
// option for sending streams at execute time
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
// length of buffer used to retrieve information for client and server info buffers
const int INFO_BUFFER_LEN = 256;
// number of segments in a version resource
const int VERSION_SUBVERSIONS = 4;
// *** internal function prototypes ***
sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, zval const* options_z, char const* _FN_ TSRMLS_DC );
SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, const char* server, zval const* options,
__inout std::string& connection_string TSRMLS_DC );
bool mark_params_by_reference( zval** params_zz, char const* _FN_ TSRMLS_DC );
void sqlsrv_conn_common_close( sqlsrv_conn* c, const char* function, bool check_errors TSRMLS_DC );
}
// constants for parameters used by process_params function(s)
int sqlsrv_conn::descriptor;
char* sqlsrv_conn::resource_name = "sqlsrv_conn";
// connection specific parameter proccessing. Use the generic function specialised to return a connection
// resource.
#define PROCESS_PARAMS( rsrc, function, param_spec, ... ) \
rsrc = process_params<sqlsrv_conn>( INTERNAL_FUNCTION_PARAM_PASSTHRU, LOG_CONN, function, param_spec, __VA_ARGS__ ); \
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
namespace ConnOptions {
const char APP[] = "APP";
const char ConnectionPooling[] = "ConnectionPooling";
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char LoginTimeout[] = "LoginTimeout";
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";
}
// 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 SQL Native Client.
//
// $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 );
SQLRETURN r = SQL_SUCCESS;
int zr = SUCCESS;
std::string conn_str;
char const* server = NULL;
zval *options_z = NULL;
int server_len;
SQLSMALLINT output_conn_size;
DECL_FUNC_NAME( "sqlsrv_connect" );
LOG_FUNCTION;
reset_errors( TSRMLS_C );
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len,
&options_z ) == FAILURE ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
conn_str.reserve( DEFAULT_CONN_STR_LEN );
emalloc_auto_ptr<sqlsrv_conn> conn;
conn = static_cast<sqlsrv_conn*>( emalloc( sizeof( sqlsrv_conn )));
hash_auto_ptr stmts;
ALLOC_HASHTABLE( stmts );
zr = zend_hash_init( stmts, 10, NULL, sqlsrv_stmt_hash_dtor, 0 );
if( zr == FAILURE ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_ZEND_HASH TSRMLS_CC );
RETURN_FALSE;
}
conn->ctx.handle = NULL;
conn->ctx.handle_type = SQL_HANDLE_DBC;
conn->in_transaction = false;
SQLHANDLE henv = g_henv_cp; // by default use the connection pooling henv
// check the connection pooling setting to determine which henv to use to allocate the connection handle
if( options_z ) {
zval** option_zz = NULL;
int zr = SUCCESS;
zr = zend_hash_find( Z_ARRVAL_P( options_z ), const_cast<char*>( ConnOptions::ConnectionPooling ),
sizeof( ConnOptions::ConnectionPooling ), reinterpret_cast<void**>( &option_zz ));
// if the option was found and it's not true, then use the non pooled environment handle
if( zr != FAILURE && !zend_is_true( *option_zz )) {
henv = g_henv_ncp;
}
}
SQLSRV_G( henv_context )->ctx.handle = henv;
r = SQLAllocHandle( SQL_HANDLE_DBC, henv, &conn->ctx.handle );
if( !SQL_SUCCEEDED( r )) {
handle_error( &SQLSRV_G( henv_context )->ctx, LOG_CONN, _FN_, NULL TSRMLS_CC );
RETURN_FALSE;
}
if( r == SQL_SUCCESS_WITH_INFO ) {
handle_warning( &SQLSRV_G( henv_context )->ctx, LOG_CONN, _FN_, NULL TSRMLS_CC );
}
// set the packet size to ~32k
r = SQLSetConnectAttr( conn->ctx.handle, SQL_ATTR_PACKET_SIZE, reinterpret_cast<SQLPOINTER>( DEFAULT_PACKET_SIZE ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle ); RETURN_FALSE; );
CHECK_SQL_WARNING( r ,conn, _FN_, NULL );
try {
r = build_connection_string_and_set_conn_attr( conn, server, options_z, conn_str TSRMLS_CC );
CHECK_SQL_ERROR( r, SQLSRV_G( henv_context ), _FN_, NULL,
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = NULL;
RETURN_FALSE );
}
catch( std::bad_alloc& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %s", ex.what() );
LOG( SEV_ERROR, LOG_CONN, "C++ memory allocation failure building the connection string." );
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() );
conn_str.clear();
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %s", ex.what() );
RETURN_FALSE;
}
catch( std::length_error const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
LOG( SEV_ERROR, LOG_CONN, "C++ exception returned: %s", ex.what() );
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = SQL_NULL_HANDLE;
RETURN_FALSE;
}
SQLSRV_STATIC_ASSERT( sizeof( char ) == sizeof( SQLCHAR )); // make sure that cast below is valid
r = SQLDriverConnect( conn->ctx.handle, NULL, reinterpret_cast<SQLCHAR*>( const_cast<char*>( conn_str.c_str() )),
static_cast<SQLSMALLINT>( conn_str.length() ), NULL,
0, &output_conn_size, SQL_DRIVER_NOPROMPT );
// Would rather use std::fill here, but that gives a warning about not being able to inline
// the iterator functions, so we use this instead.
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
conn_str.clear();
CHECK_SQL_ERROR( r, conn, _FN_, NULL,
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle ); conn->ctx.handle = SQL_NULL_HANDLE; RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
zr = ZEND_REGISTER_RESOURCE( return_value, conn, sqlsrv_conn::descriptor );
if( zr == FAILURE ) {
SQLFreeHandle( conn->ctx.handle_type, conn->ctx.handle );
conn->ctx.handle = SQL_NULL_HANDLE;
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_REGISTER_RESOURCE TSRMLS_CC, "connection" );
RETURN_FALSE;
}
conn->stmts = stmts;
stmts.transferred();
conn.transferred();
}
// 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 SQL Server 2005 Driver for PHP 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 )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
SQLRETURN rc;
sqlsrv_conn* conn = NULL;
DECL_FUNC_NAME( "sqlsrv_begin_transaction" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "r" );
CHECK_SQL_ERROR_EX( conn->in_transaction == true, conn, _FN_, SQLSRV_ERROR_ALREADY_IN_TXN, RETURN_FALSE );
rc = SQLSetConnectAttr( conn->ctx.handle, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( rc, conn, _FN_, NULL );
conn->in_transaction = true;
RETURN_TRUE;
}
PHP_FUNCTION( sqlsrv_client_info )
{
SQLRETURN rc;
int zr = SUCCESS;
sqlsrv_conn* conn = NULL;
zval* client_info = NULL;
SQLSMALLINT info_len = 0;
emalloc_auto_ptr<char> buffer;
emalloc_auto_ptr<char> ver;
DWORD ver_size = (~0U);
DWORD winRC = S_OK;
DWORD place_holder = 0;
UINT unused = 0;
VS_FIXEDFILEINFO* ver_info = NULL;
DWORD_PTR args[ VERSION_SUBVERSIONS ];
DECL_FUNC_NAME( "sqlsrv_client_info" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "r" );
zr = array_init( return_value );
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
rc = SQLGetInfo( conn->ctx.handle, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
ZVAL_STRINGL( client_info, buffer, info_len, 0 );
zr = add_assoc_zval( return_value, "DriverDllName", client_info );
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
buffer = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
rc = SQLGetInfo( conn->ctx.handle, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
ZVAL_STRINGL( client_info, buffer, info_len, 0 );
zr = add_assoc_zval( return_value, "DriverODBCVer", client_info );
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
buffer = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
rc = SQLGetInfo( conn->ctx.handle, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( rc, conn, _FN_, NULL, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
ZVAL_STRINGL( client_info, buffer, info_len, 0 );
zr = add_assoc_zval( return_value, "DriverVer", client_info );
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
buffer = static_cast<char*>( emalloc( MAX_PATH + 1 ));
winRC = GetModuleFileNameEx( GetCurrentProcess(), g_sqlsrv_hmodule, buffer, MAX_PATH );
CHECK_SQL_ERROR_EX( winRC == 0, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
ver_size = GetFileVersionInfoSize( buffer, &place_holder );
CHECK_SQL_ERROR_EX( ver_size == 0, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
ver = static_cast<char*>( emalloc( ver_size ) );
winRC = GetFileVersionInfo( buffer, 0, ver_size, ver );
CHECK_SQL_ERROR_EX( winRC == FALSE, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
winRC = VerQueryValue( ver, "\\", reinterpret_cast<LPVOID*>( &ver_info ), &unused );
CHECK_SQL_ERROR_EX( winRC == FALSE, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
args[0] = ver_info->dwFileVersionMS >> 16;
args[1] = ver_info->dwFileVersionMS & 0xffff;
args[2] = ver_info->dwFileVersionLS >> 16;
args[3] = ver_info->dwFileVersionLS & 0xffff;
winRC = FormatMessage( FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, "%1!d!.%2!d!.%3!d!.%4!d!", 0, 0, buffer, MAX_PATH, (va_list*) args );
CHECK_SQL_ERROR_EX( winRC == 0, conn, _FN_, SQLSRV_ERROR_FILE_VERSION, RETURN_FALSE );
MAKE_STD_ZVAL( client_info );
ZVAL_STRING( client_info, buffer, 0 );
zr = add_assoc_zval( return_value, "ExtensionVer", client_info );
CHECK_ZEND_ERROR( zr, NULL, RETURN_FALSE );
buffer.transferred();
}
// 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 );
zval* conn_r;
sqlsrv_conn* conn = NULL;
RETVAL_TRUE;
DECL_FUNC_NAME( "sqlsrv_close" );
LOG_FUNCTION;
full_mem_check(MEMCHECK_SILENT);
reset_errors( TSRMLS_C );
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r ) == FAILURE ) {
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ) == FAILURE ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
if( Z_TYPE_P( conn_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
}
conn = static_cast<sqlsrv_conn*>( zend_fetch_resource( &conn_r TSRMLS_CC, -1, "sqlsrv_conn", NULL, 1, sqlsrv_conn::descriptor ));
if( conn == NULL ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
RETURN_FALSE;
}
sqlsrv_conn_common_close( conn, _FN_, true TSRMLS_CC );
// cause any variables still holding a reference to this to be invalid so they cause
// an error when passed to a sqlsrv function. There's nothing we can do if the
// removal fails, so we just log it and move on.
int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( conn_r ));
if( zr == FAILURE ) {
LOG( SEV_ERROR, LOG_CONN, "Failed to remove connection resource %d", Z_RESVAL_P( conn_r ));
}
ZVAL_NULL( conn_r );
RETURN_TRUE;
}
// Called when a connection resource is destroyed by the Zend engine. Set in MINIT
void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC )
{
// get the structure
sqlsrv_conn *conn = static_cast<sqlsrv_conn*>( rsrc->ptr );
DECL_FUNC_NAME( "sqlsrv_conn_dtor" );
LOG_FUNCTION;
sqlsrv_conn_common_close( conn, _FN_, false TSRMLS_CC );
efree( conn );
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 SQL Server 2005 Driver for PHP 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 )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
SQLRETURN rc;
sqlsrv_conn* conn = NULL;
DECL_FUNC_NAME( "sqlsrv_commit" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "r" );
CHECK_SQL_ERROR_EX( conn->in_transaction == false, conn, _FN_, SQLSRV_ERROR_NOT_IN_TXN, RETURN_FALSE );
conn->in_transaction = false;
rc = SQLEndTran( SQL_HANDLE_DBC, conn->ctx.handle, SQL_COMMIT );
CHECK_SQL_ERROR( rc, conn, _FN_, SQLSRV_ERROR_COMMIT_FAILED, RETURN_FALSE );
CHECK_SQL_WARNING( rc, conn, _FN_, NULL );
rc = SQLSetConnectAttr( conn->ctx.handle, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( rc, conn, _FN_, SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF, RETURN_FALSE );
CHECK_SQL_WARNING( rc, conn, _FN_, NULL );
RETURN_TRUE;
}
// 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 );
sqlsrv_conn* conn = NULL;
emalloc_auto_ptr<sqlsrv_stmt> stmt;
char *sql_string = NULL;
int sql_len = 0;
zval* params_z = NULL;
zval* options_z = NULL;
SQLRETURN r;
int next_index;
DECL_FUNC_NAME( "sqlsrv_prepare" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "rs|z!a!", &sql_string, &sql_len, &params_z, &options_z );
stmt = allocate_stmt( conn, options_z, _FN_ TSRMLS_CC );
if( stmt.get() == NULL ) {
RETURN_FALSE;
}
SQLSRV_STATIC_ASSERT( sizeof(SQLCHAR) == sizeof(char) );
r = SQLPrepare( stmt->ctx.handle, reinterpret_cast<SQLCHAR*>( sql_string ), SQL_NTS );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, free_odbc_resources( stmt TSRMLS_CC ); RETURN_FALSE );
CHECK_SQL_WARNING( r, stmt, _FN_, NULL );
stmt->prepared = true;
if( !mark_params_by_reference( &params_z, _FN_ TSRMLS_CC )) {
RETURN_FALSE;
}
stmt->params_z = params_z;
zval_auto_ptr stmt_z;
ALLOC_INIT_ZVAL( stmt_z );
int zr = ZEND_REGISTER_RESOURCE( stmt_z, stmt, sqlsrv_stmt::descriptor );
if( zr == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_REGISTER_RESOURCE TSRMLS_CC, "statement" );
RETURN_FALSE;
}
next_index = zend_hash_next_free_element( conn->stmts );
if( zend_hash_index_update( conn->stmts, next_index, &stmt_z, sizeof( zval* ), NULL ) == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_ZEND_HASH TSRMLS_CC );
RETURN_FALSE;
}
stmt->conn_index = next_index;
stmt.transferred();
zval_ptr_dtor( &return_value );
*return_value_ptr = stmt_z;
zval_add_ref( &stmt_z ); // two references to the zval, one returned and another in the connection
stmt_z.transferred();
}
// 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 );
sqlsrv_conn* conn = NULL;
emalloc_auto_ptr<sqlsrv_stmt> stmt;
SQLCHAR *sql_string = NULL;
int sql_len = 0;
zval* params_z = NULL;
zval* options_z = NULL;
bool executed = false;
int next_index = 0;
DECL_FUNC_NAME( "sqlsrv_query" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "rs|z!a!", &sql_string, &sql_len, &params_z, &options_z );
stmt = allocate_stmt( conn, options_z, _FN_ TSRMLS_CC );
if( stmt.get() == NULL ) {
RETURN_FALSE;
}
if( !mark_params_by_reference( &params_z, _FN_ TSRMLS_CC )) {
free_odbc_resources( stmt TSRMLS_CC );
RETURN_FALSE;
}
stmt->params_z = params_z;
executed = sqlsrv_stmt_common_execute( stmt, sql_string, sql_len, true, _FN_ TSRMLS_CC );
if( !executed ) {
free_odbc_resources( stmt TSRMLS_CC );
RETURN_FALSE;
}
zval_auto_ptr stmt_z;
ALLOC_INIT_ZVAL( stmt_z );
int zr = ZEND_REGISTER_RESOURCE( stmt_z, stmt, sqlsrv_stmt::descriptor );
if( zr == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_REGISTER_RESOURCE TSRMLS_CC, "statement" );
RETURN_FALSE;
}
next_index = zend_hash_next_free_element( conn->stmts );
if( zend_hash_index_update( conn->stmts, next_index, &stmt_z, sizeof( zval* ), NULL ) == FAILURE ) {
free_odbc_resources( stmt TSRMLS_CC );
free_php_resources( stmt_z TSRMLS_CC );
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_ZEND_HASH TSRMLS_CC );
RETURN_FALSE;
}
stmt->conn_index = next_index;
stmt.transferred();
zval_ptr_dtor( &return_value );
*return_value_ptr = stmt_z;
zval_add_ref( &stmt_z ); // two references to the zval, one returned and another in the connection
stmt_z.transferred();
}
// 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 SQL Server 2005 Driver for PHP 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 )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
SQLRETURN rc;
sqlsrv_conn* conn = NULL;
DECL_FUNC_NAME( "sqlsrv_rollback" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "r" );
CHECK_SQL_ERROR_EX( conn->in_transaction == false, conn, _FN_, SQLSRV_ERROR_NOT_IN_TXN, RETURN_FALSE );
conn->in_transaction = false;
rc = SQLEndTran( SQL_HANDLE_DBC, conn->ctx.handle, SQL_ROLLBACK );
CHECK_SQL_ERROR( rc, conn, _FN_, SQLSRV_ERROR_ROLLBACK_FAILED, RETURN_FALSE );
CHECK_SQL_WARNING( rc, conn, _FN_, NULL );
rc = SQLSetConnectAttr( conn->ctx.handle, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( rc, conn, _FN_, SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF, RETURN_FALSE );
CHECK_SQL_WARNING( rc, conn, _FN_, NULL );
RETURN_TRUE;
}
// 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 )
{
SQLSRV_UNUSED( return_value_used );
SQLSRV_UNUSED( this_ptr );
SQLSRV_UNUSED( return_value_ptr );
SQLRETURN r;
int zr = SUCCESS;
sqlsrv_conn* conn = NULL;
zval* server_info;
emalloc_auto_ptr<char> p;
SQLSMALLINT info_len;
DECL_FUNC_NAME( "sqlsrv_server_info" );
LOG_FUNCTION;
PROCESS_PARAMS( conn, _FN_, "r" );
zr = array_init( return_value );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_SERVER_INFO, RETURN_FALSE );
p = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
r = SQLGetInfo( conn->ctx.handle, SQL_DATABASE_NAME, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
MAKE_STD_ZVAL( server_info );
ZVAL_STRINGL( server_info, p, info_len, 0 );
zr = add_assoc_zval( return_value, "CurrentDatabase", server_info );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_SERVER_INFO, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
p.transferred();
p = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
r = SQLGetInfo( conn->ctx.handle, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
MAKE_STD_ZVAL( server_info );
ZVAL_STRINGL( server_info, p, info_len, 0 );
zr = add_assoc_zval( return_value, "SQLServerVersion", server_info );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_SERVER_INFO, RETURN_FALSE );
p.transferred();
p = static_cast<char*>( emalloc( INFO_BUFFER_LEN ));
r = SQLGetInfo( conn->ctx.handle, SQL_SERVER_NAME, p, INFO_BUFFER_LEN, &info_len );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, RETURN_FALSE );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
MAKE_STD_ZVAL( server_info );
ZVAL_STRINGL( server_info, p, info_len, 0 );
zr = add_assoc_zval( return_value, "SQLServerName", server_info );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_SERVER_INFO, RETURN_FALSE );
p.transferred();
}
// internal connection functions
namespace {
// common close, used by close and dtor
void sqlsrv_conn_common_close( __inout sqlsrv_conn* conn, const char* _FN_, bool check_errors TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
if( conn->ctx.handle == NULL )
return;
// close the statements and roll back transactions
if( conn->stmts ) {
zend_hash_destroy( conn->stmts );
FREE_HASHTABLE( conn->stmts );
conn->stmts = NULL;
}
else {
DIE( "Connection doesn't contain a statement array!" );
}
// rollback any transaction in progress (we don't care about the return result)
SQLEndTran( SQL_HANDLE_DBC, conn->ctx.handle, SQL_ROLLBACK );
// disconnect from the server
r = SQLDisconnect( conn->ctx.handle );
if( check_errors ) { CHECK_SQL_ERROR( r, conn, _FN_, NULL, NULL, 1==1 ); }
// free the connection handle
r = SQLFreeHandle( SQL_HANDLE_DBC, conn->ctx.handle );
if( check_errors ) { CHECK_SQL_ERROR( r, conn, _FN_, NULL, 1 == 1 ); }
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
conn->ctx.handle = NULL;
}
#define NO_ATTRIBUTE -1
// type of connection attributes
enum CONN_ATTR_TYPE {
CONN_ATTR_INT,
CONN_ATTR_BOOL,
CONN_ATTR_STRING,
};
// list of valid attributes used by validate_connection_attribute below
struct connection_attribute {
const char * name;
int name_len;
enum CONN_ATTR_TYPE value_type;
int attr;
bool add;
} conn_attrs[] = {
{ ConnOptions::APP, sizeof( ConnOptions::APP ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::ConnectionPooling, sizeof( ConnOptions::ConnectionPooling ), CONN_ATTR_BOOL, NO_ATTRIBUTE, false },
{ ConnOptions::Database, sizeof( ConnOptions::Database ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::Encrypt, sizeof( ConnOptions::Encrypt ), CONN_ATTR_BOOL, NO_ATTRIBUTE, true },
{ ConnOptions::Failover_Partner, sizeof( ConnOptions::Failover_Partner ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::LoginTimeout, sizeof( ConnOptions::LoginTimeout ), CONN_ATTR_INT, SQL_ATTR_LOGIN_TIMEOUT, true },
{ ConnOptions::PWD, sizeof( ConnOptions::PWD ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::QuotedId, sizeof( ConnOptions::QuotedId ), CONN_ATTR_BOOL, NO_ATTRIBUTE, true },
{ ConnOptions::TraceFile, sizeof( ConnOptions::TraceFile ), CONN_ATTR_STRING, SQL_ATTR_TRACEFILE, true },
{ ConnOptions::TraceOn, sizeof( ConnOptions::TraceOn ), CONN_ATTR_BOOL, SQL_ATTR_TRACE, true },
{ ConnOptions::TrustServerCertificate, sizeof( ConnOptions::TrustServerCertificate ), CONN_ATTR_BOOL, NO_ATTRIBUTE, true },
{ ConnOptions::TransactionIsolation, sizeof( ConnOptions::TransactionIsolation ), CONN_ATTR_INT, SQL_COPT_SS_TXN_ISOLATION, true },
{ ConnOptions::UID, sizeof( ConnOptions::UID ), CONN_ATTR_STRING, NO_ATTRIBUTE, true },
{ ConnOptions::WSID, sizeof( ConnOptions::WSID ), CONN_ATTR_STRING, NO_ATTRIBUTE, true }
};
// return structure from validate
typedef
struct _attr_return {
bool success; // if the attribute was validated
// if it's a connection attribute rather than a connection string keyword, this is set to the attribute (SQL_ATTR_*)
// or set to NO_ATTRIBUTE if it's not a connection attribute
int attr;
// the value of the attribute if it's a connection attribute rather than a connection string keyword
int value;
char* str_value; // connection string keyword if that's what it is
unsigned int str_len; // length of the connection string keyword (save ourselves a strlen)
bool add; // see build_connect_string_and_attr for this field's use
}
attr_return;
// validates a single key/value pair from the attributes given to sqlsrv_connect.
// to validate means to verify that it is a legal key from the list of keys in conn_attrs (above)
// and to verify the type.
// string attributes are scanned to make sure that all } are properly escaped as }}.
const attr_return validate_connection_attribute( sqlsrv_conn const* conn, const char* key, int key_len, zval const* value_z TSRMLS_DC )
{
int attr_idx = 0;
attr_return ret;
// initialize our default return values
ret.success = false;
ret.attr = NO_ATTRIBUTE;
ret.value = 0;
ret.str_value = NULL;
ret.str_len = 0;
for( attr_idx = 0; attr_idx < ( sizeof( conn_attrs ) / sizeof( conn_attrs[0] )); ++attr_idx ) {
if( key_len == conn_attrs[ attr_idx ].name_len && !stricmp( key, conn_attrs[ attr_idx ].name )) {
switch( conn_attrs[ attr_idx ].value_type ) {
case CONN_ATTR_BOOL:
// bool attributes can be either strings to be appended to the connection string
// as yes or no or integral connection attributes. This will have to be reworked
// if we ever introduce a boolean connection option that maps to a string connection
// attribute.
ret.success = true;
ret.attr = conn_attrs[ attr_idx ].attr;
ret.add = conn_attrs[ attr_idx ].add;
// here we short circuit the ConnectionPooling option because it was already handled
if( conn_attrs[ attr_idx ].name == ConnOptions::ConnectionPooling ) {
return ret;
}
if( zend_is_true( const_cast<zval*>( value_z ))) {
if( ret.attr == NO_ATTRIBUTE ) {
ret.str_value = estrdup( "yes" ); // for connection strings
ret.str_len = 3;
}
else {
ret.value = true; // for connection attributes
}
}
else {
if( ret.attr == NO_ATTRIBUTE ) {
ret.str_value = estrdup( "no" ); // for connection strings
ret.str_len = 2;
}
else {
ret.value = false; // for connection attributes
}
}
break;
case CONN_ATTR_INT:
{
CHECK_SQL_ERROR_EX( Z_TYPE_P( value_z ) != IS_LONG, conn, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION, ret.success = false; return ret; );
ret.value = Z_LVAL_P( value_z );
ret.attr = conn_attrs[ attr_idx ].attr;
ret.success = true;
ret.add = conn_attrs[ attr_idx ].add;
return ret;
}
case CONN_ATTR_STRING:
{
CHECK_SQL_ERROR_EX( Z_TYPE_P( value_z ) != IS_STRING, conn, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION, ret.success = false; return ret; );
char* value = Z_STRVAL_P( value_z );
int value_len = Z_STRLEN_P( value_z );
ret.success = true;
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// unquoted since we quote it when adding it to the connection string.
if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
++value;
--value_len;
value = estrndup( value, value_len );
value[ value_len - 1 ] = '\0';
}
// duplicate the string so when we free it, it won't be a user string we're freeing
else {
value = estrndup( value, value_len );
}
ret.attr = conn_attrs[ attr_idx ].attr;
ret.add = conn_attrs[ attr_idx ].add;
ret.str_value = value;
ret.str_len = value_len;
// check to make sure that all right braces are escaped
int i = 0;
while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) {
// skip both braces
if( value[i] == '}' )
++i;
++i;
}
if( value[i] == '}' ) {
handle_error( &conn->ctx, LOG_CONN, "sqlsrv_connect", SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED TSRMLS_CC, key );
efree( value );
ret.str_value = NULL;
ret.success = false;
return ret;
}
break;
}
}
return ret;
}
}
handle_error( &conn->ctx, LOG_CONN, "sqlsrv_connect", SQLSRV_ERROR_INVALID_OPTION TSRMLS_CC, key );
return ret;
}
// 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.
SQLRETURN build_connection_string_and_set_conn_attr( sqlsrv_conn const* conn, const char* server, zval const* options,
__inout std::string& connection_string TSRMLS_DC )
{
bool credentials_mentioned = false;
attr_return ret;
int zr = SUCCESS;
DECL_FUNC_NAME( "sqlsrv_connect" );
// put the driver and server as the first components of the connection string
connection_string = "Driver={SQL Native Client};Server=";
connection_string += server;
connection_string += ";";
// if no options were given, then we make integrated auth and MARS the defaults and return immediately.
if( options == NULL ) {
connection_string += "Trusted_Connection={Yes};Mars_Connection={Yes}";
return SQL_SUCCESS;
}
if( Z_TYPE_P( options ) != IS_ARRAY ) { DIE( "Passed an invalid type for the options array" ); }
HashTable* oht = Z_ARRVAL_P( options );
for( zend_hash_internal_pointer_reset( oht );
zend_hash_has_more_elements( oht ) == SUCCESS;
zend_hash_move_forward( oht )) {
int type;
char *key;
unsigned int key_len;
unsigned long index;
zval** data;
type = zend_hash_get_current_key_ex( oht, &key, &key_len, &index, 0, NULL );
CHECK_SQL_ERROR_EX( type != HASH_KEY_IS_STRING, conn, _FN_, SQLSRV_ERROR_INVALID_CONNECTION_KEY, return SQL_ERROR )
zr = zend_hash_get_current_data( oht, (void**) &data );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return SQL_ERROR; );
ret = validate_connection_attribute( conn, key, key_len, *data TSRMLS_CC );
if( !ret.success ) {
return SQL_ERROR;
}
// if a user id is given ,then don't use a trusted connection
if( !stricmp( key, "UID" )) {
credentials_mentioned = true;
}
if( NO_ATTRIBUTE == ret.attr ) {
// some options are already handled (e.g., ConnectionPooling) so don't add them to the connection string
if( ret.add ) {
// if it's not an attribute, then it's a connection string keyword. Add it quoted.
connection_string += key;
connection_string += "={";
connection_string += ret.str_value;
connection_string += "};";
}
}
else {
SQLRETURN r;
// if it's a string attribute, the str_value member will be the string to set, otherwise the str_value will be null
// and ret.value will be the integer value to set the attribute to.
if( ret.str_value == NULL ) {
r = SQLSetConnectAttr( conn->ctx.handle, ret.attr, reinterpret_cast<SQLPOINTER>( ret.value ), SQL_IS_UINTEGER );
}
else {
r = SQLSetConnectAttr( conn->ctx.handle, ret.attr, reinterpret_cast<SQLPOINTER>( const_cast<char*>( ret.str_value )), ret.str_len );
}
CHECK_SQL_ERROR( r, conn, "sqlsrv_connect", NULL, return SQL_ERROR );
CHECK_SQL_WARNING( r, conn, "sqlsrv_connect", NULL );
}
if( ret.str_value != NULL ) {
efree( ret.str_value );
}
}
// trusted connection is the default if no user id was given.
if( !credentials_mentioned ) {
connection_string += "Trusted_Connection={Yes};";
}
// always have mars enabled.
connection_string += "Mars_Connection={Yes};";
return SQL_SUCCESS;
}
// common code to allocate a statement from either sqlsrv_prepare or sqlsv_query. Returns either
// a valid sqlsrv_stmt or NULL if an error occurred.
sqlsrv_stmt* allocate_stmt( __in sqlsrv_conn* conn, zval const* options_z, char const* _FN_ TSRMLS_DC )
{
SQLRETURN r = SQL_SUCCESS;
emalloc_auto_ptr<sqlsrv_stmt> stmt;
stmt = static_cast<sqlsrv_stmt*>( emalloc( sizeof( sqlsrv_stmt )));
emalloc_auto_ptr<char> param_buffer;
param_buffer = static_cast<char*>( emalloc( PHP_STREAM_BUFFER_SIZE ));
stmt->ctx.handle_type = SQL_HANDLE_STMT;
stmt->conn = conn;
stmt->executed = false;
stmt->prepared = false;
stmt->current_parameter = NULL;
stmt->current_parameter_read = 0;
stmt->params_z = NULL;
stmt->param_datetime_buffers = NULL;
stmt->param_buffer = param_buffer;
stmt->param_buffer_size = PHP_STREAM_BUFFER_SIZE;
stmt->send_at_exec = true;
stmt->conn_index = -1;
stmt->fetch_fields = NULL;
stmt->fetch_fields_count = 0;
stmt->new_result_set();
stmt->active_stream = NULL;
r = SQLAllocHandle( SQL_HANDLE_STMT, conn->ctx.handle, &stmt->ctx.handle );
LOG( SEV_NOTICE, LOG_STMT, "SQLAllocHandle for statement = %1!08x!", stmt->ctx.handle );
CHECK_SQL_ERROR( r, conn, _FN_, NULL, return NULL; );
CHECK_SQL_WARNING( r, conn, _FN_, NULL );
// process the options array given to sqlsrv_prepare or sqlsrv_query. see those functions
// for valid values here.
if( options_z ) {
for( zend_hash_internal_pointer_reset( Z_ARRVAL_P( options_z ));
zend_hash_has_more_elements( Z_ARRVAL_P( options_z )) == SUCCESS;
zend_hash_move_forward( Z_ARRVAL_P( options_z )) ) {
char *key = NULL;
unsigned int key_len = 0;
unsigned long index = 0;
zval** value_z = NULL;
int type = zend_hash_get_current_key_ex( Z_ARRVAL_P( options_z ), &key, &key_len, &index, 0, NULL );
if( type != HASH_KEY_IS_STRING ) {
std::ostringstream itoa;
itoa << index;
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_KEY TSRMLS_CC, itoa.str() );
SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle );
return NULL;
}
int zr = zend_hash_get_current_data( Z_ARRVAL_P( options_z ), (void**) &value_z );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return NULL );
if( !stricmp( key, QUERY_TIMEOUT )) {
if( Z_TYPE_P( *value_z ) != IS_LONG ) {
convert_to_string( *value_z );
handle_error( &conn->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_VALUE TSRMLS_CC, Z_STRVAL_PP( value_z ), key );
SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle );
return NULL;
}
r = SQLSetStmtAttr( stmt->ctx.handle, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( *value_z )), SQL_IS_UINTEGER );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle ); return NULL; );
char lock_timeout_sql[ 1024 ];
int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", Z_LVAL_PP( value_z ) * 1000 );
if( written == -1 || written == sizeof( lock_timeout_sql )) {
DIE( "sprintf_s failed. Shouldn't ever fail." );
}
r = SQLExecDirect( stmt->ctx.handle, reinterpret_cast<SQLCHAR*>( lock_timeout_sql ), SQL_NTS );
CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle ); return NULL; );
}
else if( key_len == ( sizeof( SEND_STREAMS_AT_EXEC )) && !stricmp( key, SEND_STREAMS_AT_EXEC )) {
stmt->send_at_exec = ( zend_is_true( *value_z )) ? true : false;
}
// if didn't match one of the standard options, then the key is an error
else {
handle_error( &stmt->ctx, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_OPTION_KEY TSRMLS_CC, key );
SQLFreeHandle( stmt->ctx.handle_type, stmt->ctx.handle );
return NULL;
}
}
zend_hash_internal_pointer_end( Z_ARRVAL_P( options_z ));
}
sqlsrv_stmt* ret = stmt;
stmt.transferred();
param_buffer.transferred();
return ret;
}
// mark parameters passed into sqlsrv_prepare and sqlsrv_query as reference parameters so that
// they may be updated later in the script and subsequent sqlsrv_execute calls will use the
// new values.
bool mark_params_by_reference( __inout zval** params_zz, char const* _FN_ TSRMLS_DC )
{
// if it's a NULL pointer, just return with no errors
if( !*params_zz )
return true; // continue with no errors
// if it's not an array, then return an error
if( Z_TYPE_PP( params_zz ) != IS_ARRAY ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ );
return false;
}
// process the parameter array
zval_add_ref( params_zz );
HashTable* params_ht = Z_ARRVAL_PP( params_zz );
zval** param = NULL;
int zr = SUCCESS;
// 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.
int i = 0;
for( i = 1, zend_hash_internal_pointer_reset( params_ht );
zend_hash_has_more_elements( params_ht ) == SUCCESS;
zend_hash_move_forward( params_ht ), ++i ) {
zr = zend_hash_get_current_data_ex( params_ht, reinterpret_cast<void**>( &param ), NULL );
CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, zval_ptr_dtor( params_zz ); return false; );
// if it's a sole variable
if( Z_TYPE_PP( param ) != IS_ARRAY ) {
(*param)->is_ref = 1; // mark it as a reference
}
// else mark [0] as a reference
else {
zval** var = NULL;
zr = zend_hash_index_find( Z_ARRVAL_PP( param ), 0, reinterpret_cast<void**>( &var ));
if( zr == FAILURE ) {
handle_error( NULL, LOG_CONN, _FN_, SQLSRV_ERROR_VAR_REQUIRED TSRMLS_CC, i );
zval_ptr_dtor( params_zz );
return false;
}
(*var)->is_ref = 1;
}
}
return true;
}
} // namespace