//---------------------------------------------------------------------------------------------------------------------------------- // 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 #include #include #include #include // *** 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( 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 conn; conn = static_cast( 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( ConnOptions::ConnectionPooling ), sizeof( ConnOptions::ConnectionPooling ), reinterpret_cast( &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( 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( 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( 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( 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( 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( const_cast( conn_str.c_str() )), static_cast( 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( 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( 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 buffer; emalloc_auto_ptr 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( 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( 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( 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( 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( 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( &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( 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( 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( 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 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, ¶ms_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( 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( ¶ms_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 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, ¶ms_z, &options_z ); stmt = allocate_stmt( conn, options_z, _FN_ TSRMLS_CC ); if( stmt.get() == NULL ) { RETURN_FALSE; } if( !mark_params_by_reference( ¶ms_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( 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 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( 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( 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( 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( 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( ret.value ), SQL_IS_UINTEGER ); } else { r = SQLSetConnectAttr( conn->ctx.handle, ret.attr, reinterpret_cast( const_cast( 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 stmt; stmt = static_cast( emalloc( sizeof( sqlsrv_stmt ))); emalloc_auto_ptr param_buffer; param_buffer = static_cast( 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( 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( 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( ¶m ), 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( &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