diff --git a/README.TXT b/README.TXT new file mode 100644 index 00000000..6136b7f3 --- /dev/null +++ b/README.TXT @@ -0,0 +1,34 @@ + +*Notes on compiling SQL Server 2005 Driver for PHP* + +Prerequisites: You must first be able to build PHP not including +this extension. For help on doing so, see the official PHP website, +http://php.net. + +To compile SQL Server 2005 Driver for PHP: + +1) Copy the source code into the ext\sqlsrv directory. + +2) run buildconf.bat + +3) run "cscript configure.js --enable-sqlsrv=shared " 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 live.com for configurations that have worked for other people. + + 3a) It might be possible to compile with just --enable-sqlsrv rather + than --enable-sqlsrv=shared, but that configuration has not been tested. + +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 to your PHP extension directory. + +This software has been compiled and tested under PHP 5.2.6 and PHP 5.2.4 +using the Visual C++ 2005 Express and Visual C++ 2008 Standard compilers. + +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. diff --git a/config.w32 b/config.w32 new file mode 100644 index 00000000..8cf2b6d0 --- /dev/null +++ b/config.w32 @@ -0,0 +1,40 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: config.w32 +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Contents: JScript build configuration used by buildconf.bat +// +// 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. +//---------------------------------------------------------------------------------------------------------------------------------- + +ARG_ENABLE("sqlsrv", "enable MS SQL Server extension", "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 stream.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' ); + ADD_FLAG( 'CFLAGS_SQLSRV', '/EHsc' ); + // if debug is included, the math.h header file defines a number of inline functions which are + // not inlined when debug is enabled (optimizations disabled). This results in duplicate entries + // in each obj file, which prevents proper linking. + if( PHP_DEBUG == "yes" ) { + ADD_FLAG( 'CFLAGS_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); + } + ADD_FLAG( 'CFLAGS_SQLSRV', '/WX' ); + ADD_FLAG( 'CFLAGS_SQLSRV', '/GS' ); + // a template is defined in this header file which doesn't allow the extension to compile + // because we've wrapped our PHP headers with extern "C". The sqlsrv extension uses none of + // the functionality in WinSock, so we define the include guard to prevent the file from being included. + // This is required for Visual C++ 2005 Express with the Platform SDK 2003 R2. + ADD_FLAG( 'CFLAGS_SQLSRV', '/D_WSPIAPI_H_' ); + } +} diff --git a/conn.cpp b/conn.cpp new file mode 100644 index 00000000..d7f60ee5 --- /dev/null +++ b/conn.cpp @@ -0,0 +1,1262 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// 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 diff --git a/init.cpp b/init.cpp new file mode 100644 index 00000000..0a3ae0e3 --- /dev/null +++ b/init.cpp @@ -0,0 +1,641 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: init.cpp +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Contents: The initialization routines for the SQL Server 2005 Driver for PHP 1.0 +// +// 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 "version.h" + +ZEND_GET_MODULE(g_sqlsrv) + +extern "C" { + +ZEND_DECLARE_MODULE_GLOBALS(sqlsrv); + +} + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +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, "connection resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "connection resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 0 ) + ZEND_ARG_INFO( 0, "connection resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "connection resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 ) + ZEND_ARG_INFO( 0, "option name" ) + 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" ) + ZEND_ARG_ARRAY_INFO( 0, "options", 0 ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 ) + ZEND_ARG_INFO( 0, "flags (errors, warnings, or all)" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "statement resource" ) + ZEND_ARG_INFO( 0, "parameters" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "statement resource" ) + ZEND_ARG_INFO( 0, "array type" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "statement resource" ) + ZEND_ARG_INFO( 0, "class name" ) + ZEND_ARG_INFO( 0, "ctor params" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "option name" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 0, 2 ) + ZEND_ARG_INFO( 0, "statement resource" ) + ZEND_ARG_INFO( 0, "field index" ) + ZEND_ARG_INFO( 0, "type" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, "connection resource" ) + ZEND_ARG_INFO( 0, "sql command" ) + ZEND_ARG_INFO( 0, "parameters" ) + ZEND_ARG_ARRAY_INFO( 0, "options", 0 ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, "connection resource" ) + ZEND_ARG_INFO( 0, "sql command" ) + ZEND_ARG_INFO( 0, "parameters" ) + ZEND_ARG_ARRAY_INFO( 0, "options", 0 ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, "connection resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 ) + ZEND_ARG_INFO( 0, "statement resource" ) +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_begin_transaction, sqlsrv_begin_transaction_arginfo ) + PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo ) + PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo ) + PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo ) + PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo ) + PHP_FE( sqlsrv_close, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo ) + PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo ) + PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo ) + PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo ) + PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo ) + PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo ) + PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo ) + PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo ) + PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo ) + PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo ) + PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo ) + PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo ) + PHP_FE( sqlsrv_query, sqlsrv_query_arginfo ) + PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo ) + PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo ) + PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo ) + PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo ) + PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo ) + PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_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} +}; + +// module global variables (initialized in MINIT and freed in MSHUTDOWN) +HMODULE g_sqlsrv_hmodule = NULL; +HENV g_henv_ncp = SQL_NULL_HANDLE; +HENV g_henv_cp = SQL_NULL_HANDLE; + + +// 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 ); + + SQLRETURN r; + + // 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 )); + + LOG( SEV_NOTICE, LOG_INIT, "sqlsrv: entering minit" ); + + REGISTER_INI_ENTRIES(); + + DECL_FUNC_NAME( "PHP_MINIT_FUNCTION for php_sqlsrv" ); + LOG_FUNCTION; + + 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 + sqlsrv_conn::descriptor = zend_register_list_destructors_ex( + sqlsrv_conn_dtor, NULL, "SQL Server Connection", module_number ); + if( sqlsrv_conn::descriptor == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_MINIT_FUNCTION: connection resource registration failed" ); + return FAILURE; + } + + // register statement resources + sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( + sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number ); + if( sqlsrv_stmt::descriptor == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_MINIT_FUNCTION: statement resource regisration failed" ); + 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_YES", 0, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 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 ); + + 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_UNCOMMITTED, 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 ); + + if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT_FUNCTION: stream registration failed" ); + return FAILURE; + } + + g_henv_ncp = SQL_NULL_HANDLE; + g_henv_cp = SQL_NULL_HANDLE; + + // allocate the non pooled environment handle + r = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &(g_henv_ncp)); + if( !SQL_SUCCEEDED( r )) { + + g_henv_ncp = SQL_NULL_HANDLE; + LOG( SEV_ERROR, LOG_INIT, "SQLAllocHandle for non pooled connections failed." ); + return FAILURE; + } + + sqlsrv_henv henv_ctx; + henv_ctx.ctx.handle = g_henv_ncp; + henv_ctx.ctx.handle_type = SQL_HANDLE_ENV; + + // set to ODBC 3 + r = SQLSetEnvAttr( g_henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER ); + if( r == SQL_ERROR ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_ncp ); + g_henv_ncp = SQL_NULL_HANDLE; + LOG( SEV_ERROR, LOG_INIT, "SQLSetEnvAttr failed." ); + return FAILURE; + } + CHECK_SQL_WARNING( r, (&henv_ctx), _FN_, NULL ); + + // disable connection pooling + r = SQLSetEnvAttr( g_henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), SQL_IS_UINTEGER ); + if( r == SQL_ERROR ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_ncp ); + g_henv_ncp = SQL_NULL_HANDLE; + LOG( SEV_ERROR, LOG_INIT, "Failed to turn on connection pooling." ); + return FAILURE; + } + CHECK_SQL_WARNING( r, (&henv_ctx), _FN_, NULL ); + + // allocate the pooled envrionment handle + r = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &(g_henv_cp)); + if( r == SQL_ERROR ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_ncp ); + g_henv_ncp = SQL_NULL_HANDLE; + g_henv_cp = SQL_NULL_HANDLE; + LOG( SEV_ERROR, LOG_INIT, "SQLAllocHandle for pooled connections failed." ); + return FAILURE; + } + CHECK_SQL_WARNING( r, (&henv_ctx), _FN_, NULL ); + + // set to ODBC 3 + r = SQLSetEnvAttr( g_henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER ); + if( r == SQL_ERROR ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_ncp ); + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_cp ); + g_henv_ncp = SQL_NULL_HANDLE; + g_henv_cp = SQL_NULL_HANDLE; + LOG( SEV_ERROR, LOG_INIT, "SQLSetEnvAttr failed to set ODBC version 3." ); + return FAILURE; + } + CHECK_SQL_WARNING( r, (&henv_ctx), _FN_, NULL ); + + // enable connection pooling + r = SQLSetEnvAttr( g_henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), SQL_IS_UINTEGER ); + if( r == SQL_ERROR ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_ncp ); + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_cp ); + g_henv_ncp = SQL_NULL_HANDLE; + g_henv_cp = SQL_NULL_HANDLE; + LOG( SEV_ERROR, LOG_INIT, "Failed to turn on connection pooling." ); + return FAILURE; + } + CHECK_SQL_WARNING( r, (&henv_ctx), _FN_, NULL ); + + 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 ); + + LOG( SEV_NOTICE, LOG_INIT, "sqlsrv: entering mshutdown" ); + + UNREGISTER_INI_ENTRIES(); + + if( g_henv_ncp != SQL_NULL_HANDLE ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_ncp ); + g_henv_ncp = SQL_NULL_HANDLE; + } + + if( g_henv_cp != SQL_NULL_HANDLE ) { + + SQLFreeHandle( SQL_HANDLE_ENV, g_henv_cp ); + g_henv_cp = SQL_NULL_HANDLE; + } + + 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_error to_ignore; + + LOG( SEV_NOTICE, LOG_INIT, "sqlsrv: entering rinit" ); + SQLSRV_G( log_subsystems ) = 0; + SQLSRV_G( log_severity ) = SEV_ERROR; + SQLSRV_G( warnings_return_as_errors ) = true; + SQLSRV_G( henv_context ) = static_cast( emalloc( sizeof( sqlsrv_henv ))); + SQLSRV_G( henv_context )->ctx.handle = g_henv_cp; + SQLSRV_G( henv_context )->ctx.handle_type = SQL_HANDLE_ENV; + ALLOC_INIT_ZVAL( SQLSRV_G( errors )); + SQLSRV_G( errors )->is_ref = 1; + ALLOC_INIT_ZVAL( SQLSRV_G( warnings )); + SQLSRV_G( warnings )->is_ref = 1; + + // read INI settings + SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS ); + SQLSRV_G( log_severity ) = INI_BOOL( INI_PREFIX INI_LOG_SEVERITY ); + SQLSRV_G( log_subsystems ) = INI_BOOL( INI_PREFIX INI_LOG_SUBSYSTEMS ); + + LOG( SEV_NOTICE, LOG_INIT, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); + LOG( SEV_NOTICE, LOG_INIT, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); + LOG( SEV_NOTICE, LOG_INIT, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); + + // initialize list of warnings to ignore + ALLOC_HASHTABLE( SQLSRV_G( warnings_to_ignore )); + int zr = zend_hash_init( SQLSRV_G( warnings_to_ignore ), 5, NULL, NULL, 0 ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + + // changed database warning + to_ignore.sqlstate = "01000"; + to_ignore.native_message = NULL; + to_ignore.native_code = 5701; + to_ignore.format = false; + zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + // changed language warning + to_ignore.sqlstate = "01000"; + to_ignore.native_message = NULL; + to_ignore.native_code = 5703; + to_ignore.format = false; + zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + // option value changed + to_ignore.sqlstate = "01S02"; + to_ignore.native_message = NULL; + to_ignore.native_code = -1; + to_ignore.format = false; + zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + // cursor operation conflict + to_ignore.sqlstate = "01001"; + to_ignore.native_message = NULL; + to_ignore.native_code = -1; + to_ignore.format = false; + zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + // null value eliminiated in set function + to_ignore.sqlstate = "01003"; + to_ignore.native_message = NULL; + to_ignore.native_code = -1; + to_ignore.format = false; + zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + // packet size warning + to_ignore.sqlstate = "01S02"; + to_ignore.native_message = NULL; + to_ignore.native_code = 0; + to_ignore.format = false; + zr = zend_hash_next_index_insert( SQLSRV_G( warnings_to_ignore ), &to_ignore, sizeof( sqlsrv_error ), NULL ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_INIT, "PHP_RINIT: warnings hash table failure" ); + return FAILURE; + } + + // 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( SEV_NOTICE, LOG_INIT, "sqlsrv: entering rshutdown" ); + reset_errors( TSRMLS_C ); + + zval_ptr_dtor( &SQLSRV_G( errors )); + zval_ptr_dtor( &SQLSRV_G( warnings )); + if( SQLSRV_G( warnings_to_ignore )) { + zend_hash_destroy( SQLSRV_G( warnings_to_ignore )); + FREE_HASHTABLE( SQLSRV_G( warnings_to_ignore )); + } + efree( SQLSRV_G( henv_context )); + + // 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"); + DISPLAY_INI_ENTRIES(); + php_info_print_table_end(); +} + + +// 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; +} diff --git a/php_sqlsrv.h b/php_sqlsrv.h new file mode 100644 index 00000000..fcb466a1 --- /dev/null +++ b/php_sqlsrv.h @@ -0,0 +1,1082 @@ +#ifndef PHP_SQLSRV_H +#define PHP_SQLSRV_H + +//---------------------------------------------------------------------------------------------------------------------------------- +// File: php_sqlsrv.h +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Contents: Declarations for the SQL Server 2005 Driver for PHP 1.0 +// +// Comments: Also contains "internal" declarations shared across source files. +// +// 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. +//---------------------------------------------------------------------------------------------------------------------------------- + +#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_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." ) +#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 +#include + +// 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 + +#include +#include + +// borrowed from sqlncli.h to not require the SQL Server SDK to build +#define SQL_SS_LENGTH_UNLIMITED 0 +#define SQL_SS_XML (-152) +#define SQL_SS_UDT (-151) +#define SQL_COPT_SS_TXN_ISOLATION 1227 +#define SQL_TXN_SS_SNAPSHOT 0x00000020L + +// static assert for enforcing compile time conditions +template +struct sqlsrv_static_assert; + +template <> +struct sqlsrv_static_assert { static const int value = 1; }; + +#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) + + +//********************************************************************************************************************************** +// Initialization Functions +//********************************************************************************************************************************** + +// 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); + +// sqlsrv_context +// a sqlsrv_context is the agnostic way to represent a handle and its type. This is used primarily when handling errors and +// warnings. We pass this in and the error handling can use the handle and its type to get the diagnostic records from +// SQLGetDiagRec. +struct sqlsrv_context { + SQLHANDLE handle; + SQLSMALLINT handle_type; +}; + +// variables set during initialization (move these to init.cpp) +extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP +extern HMODULE g_sqlsrv_hmodule; // used for getting the version information +extern SQLHANDLE g_henv_ncp; // used to create connection handles with connection pooling off +extern SQLHANDLE g_henv_cp; // used to create connection handles with connection pooling on + + +//********************************************************************************************************************************** +// Connection +//********************************************************************************************************************************** + +// *** connection resource structure *** +// this is the resource structure returned when a connection is made. +struct sqlsrv_conn { + + // instance variables + sqlsrv_context ctx; // see sqlsrv_context + HashTable* stmts; // collection of statements allocated from this connection + 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; +}; + +// environment context used by sqlsrv_connect for when a connection error occurs. +struct sqlsrv_henv { + sqlsrv_context ctx; +}; + +// *** connection functions *** +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); + +// 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 { + char* name; + unsigned int len; +}; + +// *** statement resource structure *** +struct sqlsrv_stmt { + + void new_result_set( bool release_datetime_buffers = true ); + + sqlsrv_context ctx; + sqlsrv_conn* conn; + zval* current_parameter; + unsigned int current_parameter_read; + bool executed; + bool prepared; + bool fetch_called; + // field names for the current result set for use by sqlsrv_fetch_array/object as keys + sqlsrv_fetch_field* fetch_fields; + int fetch_fields_count; + int last_field_index; + bool past_fetch_end; + bool past_next_result_end; + zval* params_z; + zval* param_datetime_buffers; + void* param_buffer; + int param_buffer_size; + bool send_at_exec; + int conn_index; + zval* active_stream; + + + // 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; +}; + +// *** 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_next_result); +PHP_FUNCTION(sqlsrv_num_fields); +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 used by sqlsrv_query and sqlsrv_close +bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function TSRMLS_DC ); +void sqlsrv_stmt_hash_dtor( void* stmt ); +void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC ); +void free_php_resources( zval* stmt_z TSRMLS_DC ); +void remove_from_connection( sqlsrv_stmt* stmt TSRMLS_DC ); + +// *** constants *** + +// 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_BINARY, // use SQL_C_BINARY when using SQLGetData + SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData +}; + +// 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; + + +// 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; + int encoding:16; + } typeinfo; + + long value; +}; + +// *** variables *** + + +//********************************************************************************************************************************** +// 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); + + +//********************************************************************************************************************************** +// Stream +//********************************************************************************************************************************** + +// stream instance variables +struct sqlsrv_stream { + sqlsrv_stmt* stmt; + int stmt_index; + SQLUSMALLINT field; + SQLSMALLINT sql_type; + int encoding; +}; + +// resource constants used when registering the stream type with PHP +#define SQLSRV_STREAM_WRAPPER "sqlsrv" +#define SQLSRV_STREAM "sqlsrv_stream" + +extern php_stream_wrapper g_sqlsrv_stream_wrapper; + + +//********************************************************************************************************************************** +// Global variables +//********************************************************************************************************************************** + +extern "C" { + +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) + +// error context for the henv when a connection fails +sqlsrv_henv* henv_context; +// 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) +unsigned int log_severity; +unsigned int log_subsystems; +zend_bool warnings_return_as_errors; +// special list of warnings to ignore even if warnings are treated as errors +HashTable* warnings_to_ignore; + +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_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 ) +PHP_INI_END() + + + +//********************************************************************************************************************************** +// Logging +//********************************************************************************************************************************** +// a simple wrapper around a PHP error logging function. +void write_to_log( unsigned int severity, unsigned int subsystem TSRMLS_DC, const char* msg, ... ); +// a macro to make it convenient to use the function. +#define LOG( severity, subsystem, msg, ...) write_to_log( severity, subsystem TSRMLS_CC, msg, __VA_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, +}; +// 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, +}; + +// a macro to declare a function name variable. This defines a variable, _FN_, for a function's scope +// that can be used throughout the function to reference it's name. We don't use the predefined __FUNCTION__ +// because Zend's PHP_FUNCTION macro mangles the name a bit. The var _FN_LEN_ is also defined for when the +// length is needed also. +#define DECL_FUNC_NAME( name ) \ + const char* _FN_ = name; \ + // const int _FN_LEN_ = sizeof( name ); + +// a macro to log entering a function. used at the top of each API function. +#define LOG_FUNCTION LOG( SEV_NOTICE, LOG_STMT, "%1!s!: entering", _FN_ ); + + +//********************************************************************************************************************************** +// 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 +//********************************************************************************************************************************** + +// *** PHP specific errors *** +// sqlsrv errors are held in a structure of this type used by handle_errors_and_warnings +// format is a flag that tells handle_errors_and_warnings if there are parameters to use with FormatMessage +// into the error message before returning it. +struct sqlsrv_error { + char const* sqlstate; + char const* native_message; + int native_code; + bool format; +}; + +// defintions for PHP specific errors returned by sqlsrv +extern sqlsrv_error SQLSRV_ERROR_INVALID_OPTION[]; +extern sqlsrv_error SQLSRV_ERROR_FILE_VERSION[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_PARAM_TYPE[]; +extern sqlsrv_error SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED[]; +extern sqlsrv_error SQLSRV_ERROR_NO_DATA[]; +extern sqlsrv_error SQLSRV_ERROR_STREAMABLE_TYPES_ONLY[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_CONNECTION_KEY[]; +extern sqlsrv_error SQLSRV_ERROR_VAR_REQUIRED[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_TYPE[]; +extern sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_EXECUTED[]; +extern sqlsrv_error SQLSRV_ERROR_ALREADY_IN_TXN[]; +extern sqlsrv_error SQLSRV_ERROR_NOT_IN_TXN[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE[]; +extern sqlsrv_error SQLSRV_ERROR_FETCH_NOT_CALLED[]; +extern sqlsrv_error SQLSRV_ERROR_FIELD_INDEX_ERROR[]; +extern sqlsrv_error SQLSRV_ERROR_DATETIME_CONVERSION_FAILED[]; +extern sqlsrv_error SQLSRV_ERROR_SERVER_INFO[]; +extern sqlsrv_error SQLSRV_ERROR_FETCH_PAST_END[]; +extern sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_PREPARED[]; +extern sqlsrv_error SQLSRV_ERROR_ZEND_HASH[]; +extern sqlsrv_error SQLSRV_ERROR_ZEND_STREAM[]; +extern sqlsrv_error SQLSRV_ERROR_NEXT_RESULT_PAST_END[]; +extern sqlsrv_error SQLSRV_ERROR_STREAM_CREATE[]; +extern sqlsrv_error SQLSRV_ERROR_NO_FIELDS[]; +extern sqlsrv_error SQLSRV_ERROR_ZEND_BAD_CLASS[]; +extern sqlsrv_error SQLSRV_ERROR_ZEND_OBJECT_FAILED[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_PRECISION[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_KEY[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_VALUE[]; +extern sqlsrv_error SQLSRV_ERROR_OUTPUT_PARAM_TYPE_DOESNT_MATCH[]; +extern sqlsrv_error SQLSRV_ERROR_INVALID_TYPE[]; +extern sqlsrv_error SQLSRV_ERROR_COMMIT_FAILED[]; +extern sqlsrv_error SQLSRV_ERROR_ROLLBACK_FAILED[]; +extern sqlsrv_error SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF[]; +extern sqlsrv_error SQLSRV_ERROR_REGISTER_RESOURCE[]; + +// definitions for PHP specific warnings returned by sqlsrv +extern sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[]; + +enum error_handling_flags { + SQLSRV_ERR_ERRORS, + SQLSRV_ERR_WARNINGS, + SQLSRV_ERR_ALL +}; + +// *** extension error functions *** +PHP_FUNCTION(sqlsrv_errors); +PHP_FUNCTION(sqlsrv_warnings); + +// *** 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 ); + +// 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 DIE( msg, ...) php_error( E_ERROR, msg, __VA_ARGS__ ); + +bool check_sql_error_ex( bool condition, sqlsrv_context const* ctx, int log_subsystem, const char* function, sqlsrv_error const* ssphp TSRMLS_DC, ... ); + +// this is a special function for sqlsrv internal warnings. It emits an internal warning and treats +// it as an error if the WarningsReturnAsErrors flag is set. +bool check_sqlsrv_warnings( bool condition, sqlsrv_context const* ctx, int log_subsystem, const char* function, sqlsrv_error const* ssphp TSRMLS_DC, ... ); + +// *** These functions are simplified if statements that take boilerplate code down to a single line to avoid distractions in the code. +// *** If you need to actually send variadic arguments for printing, then you'll have to call check_sql_error_ex directly. +// *** these macros rely on the variable current_log_subsystem. This should be defined in every subsystem to +// *** one of the constants (LOG_CONN, LOG_STMT, etc.). + +// check a generic condition and execute error handling code after posting the error to the error queue +#define CHECK_SQL_ERROR_EX( condition, resource, function, ssphp, ... ) \ + { \ + __pragma( warning( push )) \ + __pragma( warning( disable: 4714 )) \ + bool ignored = check_sql_error_ex( (condition), &(resource)->ctx, current_log_subsystem, \ + function, ssphp TSRMLS_CC ); \ + __pragma( warning( pop )) \ + if( !ignored ) { \ + __VA_ARGS__; \ + } \ + } + +// chech the SQLRETURN code after an ODBC call and post any errors to the error queue and then execute error handling code +#define CHECK_SQL_ERROR( result, resource, function, ssphp, ... ) \ + CHECK_SQL_ERROR_EX( (!SQL_SUCCEEDED( result )) || \ + ((SQLSRV_G( warnings_return_as_errors )) && \ + (result == SQL_SUCCESS_WITH_INFO )), \ + resource, function, ssphp, __VA_ARGS__ ) + +// check for warnings after an ODBC call. This simply logs the warnings that may be retrieved later. +#define CHECK_SQL_WARNING( result, resource, function, ssphp ) \ + if( result == SQL_SUCCESS_WITH_INFO && SQLSRV_G( warnings_return_as_errors ) == false ) { \ + handle_warning( &resource->ctx, current_log_subsystem, function, ssphp TSRMLS_CC ); \ + } + +// equivalent macro that checks the result of a Zend API and fails gracefully if it failed. +#define CHECK_ZEND_ERROR( result, ssphp, ... ) \ + if( result == FAILURE ) { \ + __pragma( warning( push )) \ + __pragma( warning( disable: 4714 )) \ + check_sql_error_ex( true, NULL, current_log_subsystem, _FN_, ssphp TSRMLS_CC ); \ + __pragma( warning( pop )) \ + __VA_ARGS__; \ + } \ + +#define CHECK_SQLSRV_WARNING( condition, ssphp, ... ) \ + { \ + __pragma( warning( push )) \ + __pragma( warning( disable: 4714 )) \ + bool ignored = check_sqlsrv_warnings( condition, NULL, current_log_subsystem, _FN_, ssphp TSRMLS_CC ); \ + __pragma( warning( pop )) \ + if( !ignored ) { \ + __VA_ARGS__; \ + } \ + } + +// 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 )); +} + + +//********************************************************************************************************************************** +// Utility Functions +//********************************************************************************************************************************** +// 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 = var + +// do a heap check in debug mode, but only print errors, not all of the allocations +#define MEMCHECK_SILENT 1 + + +// 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( 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'); +} + + +// generic functions used to validate parameters to a PHP function. +// Register an invalid parameter error and returns NULL when parameters don't match the spec given. +// Each function is nearly identical, except the number of parameters each accepts. +// We do this since template functions can't be variadic. + +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, int log_subsystem, char const* function, char const* param_spec ) +{ + SQLSRV_UNUSED( return_value_used ); + SQLSRV_UNUSED( this_ptr ); + SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // test the integrity of the Zend heap in debug mode + full_mem_check(MEMCHECK_SILENT); + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > 1 ) { + DIE( "Called no parameter function with parameters." ); + } + + // parse the parameters + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ) == FAILURE ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + // get the resource registered + h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + if( h == NULL ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + return h; +} + +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, int log_subsystem, char const* function, char const* param_spec, void* p1 ) +{ + SQLSRV_UNUSED( return_value_used ); + SQLSRV_UNUSED( this_ptr ); + SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // test the integrity of the Zend heap. + full_mem_check(MEMCHECK_SILENT); + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > 2 ) { + DIE( "Called 1 parameter function with more than 1 parameter." ); + } + + // parse the parameters + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, p1 ) == FAILURE ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + // get the resource registered + h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + if( h == NULL ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + return h; +} + +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, int log_subsystem, char const* function, char const* param_spec, void* p1, void* p2 ) +{ + SQLSRV_UNUSED( return_value_used ); + SQLSRV_UNUSED( this_ptr ); + SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // test the integrity of the Zend heap in debug mode + full_mem_check(MEMCHECK_SILENT); + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > 3 ) { + DIE( "Called 2 parameter function with more than 2 parameters." ); + } + + // parse the parameters + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, p1, p2 ) == FAILURE ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + // get the resource registered + h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + if( h == NULL ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + return h; +} + +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, int log_subsystem, char const* function, char const* param_spec, void* p1, void* p2, void* p3 ) +{ + SQLSRV_UNUSED( return_value_used ); + SQLSRV_UNUSED( this_ptr ); + SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // test the integrity of the Zend heap in debug mode + full_mem_check(MEMCHECK_SILENT); + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > 4 ) { + DIE( "Called 3 parameter function with more than 3 parameters." ); + } + + // parse the parameters + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, p1, p2, p3 ) == FAILURE ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + // get the resource registered + h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + if( h == NULL ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + return h; +} + + +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, int log_subsystem, char const* function, char const* param_spec, void* p1, void* p2, void* p3, void* p4 ) +{ + SQLSRV_UNUSED( return_value_used ); + SQLSRV_UNUSED( this_ptr ); + SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // test the integrity of the Zend heap in debug mode + full_mem_check(MEMCHECK_SILENT); + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > 5 ) { + DIE( "Called 4 parameter function with more than 4 parameters." ); + } + + // parse the parameters + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, p1, p2, p3, p4 ) == FAILURE ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + // get the resource registered + h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + if( h == NULL ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + return h; +} + +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, int log_subsystem, char const* function, char const* param_spec, void* p1, void* p2, void* p3, void* p4, void* p5 ) +{ + SQLSRV_UNUSED( return_value_used ); + SQLSRV_UNUSED( this_ptr ); + SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // test the integrity of the Zend heap in debug mode + full_mem_check(MEMCHECK_SILENT); + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > 6 ) { + DIE( "Called 5 parameter function with more than 5 parameters." ); + } + + // parse the parameters + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, p1, p2, p3, p4, p5 ) == FAILURE ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + // get the resource registered + h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + if( h == NULL ) { + handle_error( NULL, log_subsystem, function, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, function ); + return NULL; + } + + return h; +} + + +// trait class that allows us to assign const types to an auto_ptr +template +struct remove_const { + typedef T type; +}; + +template +struct remove_const { + typedef T* type; +}; + + +// base class for auto_ptrs that we define below. It provides common operators and functions +// used by all the classes. +template +class sqlsrv_auto_ptr { + +public: + + sqlsrv_auto_ptr( void ) : _ptr( NULL ) + { + } + + ~sqlsrv_auto_ptr( void ) + { + static_cast(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::type () const + { + return _ptr; + } + + // 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[]( unsigned short index ) const + { + return _ptr[ index ]; + } + + // access elements of a structure through the auto ptr + T* const operator->( void ) const + { + return _ptr; + } + + // cast to T** used by many functions + T** operator&() + { + return &_ptr; + } + + // value from reference operator (i.e., i = *(&i); or *i = blah;) + T& operator*() + { + return *_ptr; + } + +protected: + + sqlsrv_auto_ptr( T* ptr ) : + _ptr( ptr ) + { + } + + sqlsrv_auto_ptr( sqlsrv_auto_ptr const& src ) + { + if( _ptr ) { + static_cast(this)->reset( src._ptr ); + } + src.transferred(); + } + + sqlsrv_auto_ptr( typename Subclass const& src ) + { + if( _ptr ) { + static_cast( 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( this )->reset( ptr ); + + return ptr; + } + + T* _ptr; + +}; + +// an auto_ptr for emalloc/efree. When allocating a chunk of memory using emalloc, wrap that pointer +// in a variable of emalloc_auto_ptr. emalloc_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". +template +class emalloc_auto_ptr : public sqlsrv_auto_ptr > { + +public: + + emalloc_auto_ptr( void ) : + sqlsrv_auto_ptr >( NULL ) + { + } + + emalloc_auto_ptr( const emalloc_auto_ptr& src ) + { + sqlsrv_auto_ptr >::sqlsrv_auto_ptr( src ); + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( T* ptr = NULL ) + { + if( _ptr ) + efree( (void*) _ptr ); + _ptr = ptr; + } + + T* operator=( T* ptr ) + { + return sqlsrv_auto_ptr >::operator=( ptr ); + } +}; + + +// 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 { + +public: + + hash_auto_ptr( void ) : + sqlsrv_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::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 { + +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::operator=( ptr ); + } + +private: + + zval_auto_ptr( const zval_auto_ptr& src ); +}; + +#endif /* PHP_SQLSRV_H */ + diff --git a/stmt.cpp b/stmt.cpp new file mode 100644 index 00000000..792ec00c --- /dev/null +++ b/stmt.cpp @@ -0,0 +1,2548 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: stmt.cpp +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Contents: Routines that use statement handles +// +// 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. +//---------------------------------------------------------------------------------------------------------------------------------- + +// *** header files *** +#include "php_sqlsrv.h" + +#include + +// *** internal variables and constants *** + +// our resource descriptor assigned in minit +int sqlsrv_stmt::descriptor; +char* sqlsrv_stmt::resource_name = "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 +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 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 +const char DATETIME_CLASS_NAME[] = "DateTime"; +const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; +const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; +const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); + +// constants for maximums in SQL Server 2005 +const int SQL_SERVER_2005_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_2005_MAX_PRECISION = 38; +const int SQL_SERVER_2005_DEFAULT_PRECISION = 18; +const int SQL_SERVER_2005_DEFAULT_SCALE = 0; + +// base allocation size when retrieving a string field +const int INITIAL_FIELD_STRING_LEN = 256; +// max size of a date time string when converting from a DateTime object to a string +const int MAX_DATETIME_STRING_LEN = 256; + +// this must align 1:1 with the SQLSRV_PHPTYPE enum in php_sqlsrv.h +const zend_uchar sqlsrv_to_php_type[] = { + IS_NULL, + IS_LONG, + IS_DOUBLE, + IS_STRING, + IS_OBJECT, + IS_RESOURCE, + SQLSRV_PHPTYPE_INVALID +}; + +// 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; + +// *** internal function prototypes *** + +// These are arranged alphabetically. They are all used by the sqlsrv statement functions. +SQLSMALLINT binary_or_char_encoding( SQLSMALLINT c_type ); +bool check_for_next_stream_parameter( sqlsrv_stmt* stmt, zval* return_value TSRMLS_DC ); +void close_active_stream( sqlsrv_stmt* s TSRMLS_DC ); +SQLSMALLINT determine_c_type( int php_type, int encoding ); +bool determine_column_size_or_precision( sqlsrv_sqltype sqlsrv_type, SQLUINTEGER* column_size, SQLSMALLINT* decimal_digits ); +bool determine_param_defaults( sqlsrv_stmt const* stmt, const char* _FN_, zval const* param_z, int param_num, zend_uchar& php_type, int& direction, + sqlsrv_sqltype& sql_type, SQLSMALLINT& sql_c_type, SQLUINTEGER& column_size, SQLSMALLINT& decimal_digits TSRMLS_DC ); +sqlsrv_phptype determine_sqlsrv_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); +sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value ); +void fetch_common( sqlsrv_stmt* stmt, int fetch_type, zval* return_value, const char* _FN_, bool allow_empty_field_names TSRMLS_DC ); +void get_field_common( sqlsrv_stmt* s, const char* _FN_, sqlsrv_phptype sqlsrv_phptype, SQLUSMALLINT field_index, zval**field_value TSRMLS_DC ); +void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT field_index, zval* return_value, const char* _FN_ TSRMLS_DC ); +bool is_fixed_size_type( SQLINTEGER sql_type ); +bool is_streamable_type( SQLINTEGER sql_type ); +bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); +zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval* param_array, SQLSMALLINT param_num, int& direction, zend_uchar& php_type, SQLSMALLINT& sql_c_type, sqlsrv_sqltype& sql_type, + SQLUINTEGER& column_size, SQLSMALLINT& decimal_digits TSRMLS_DC ); +bool send_stream_packet( sqlsrv_stmt* stmt, zval* return_value, char const* _FN_ TSRMLS_DC ); +void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); +void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); +void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ); + +} + +// statement specific parameter proccessing. Uses the generic function specialised to return a statement +// resource. +#define PROCESS_PARAMS( rsrc, function, param_spec, ... ) \ + rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, LOG_STMT, const_cast( function ), param_spec, __VA_ARGS__ ); \ + if( rsrc == NULL ) { \ + RETURN_FALSE; \ + } + + +// 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stmt* stmt = NULL; + + DECL_FUNC_NAME( "sqlsrv_cancel" ); + LOG_FUNCTION; + + // take only the statement resource + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + r = SQLCancel( stmt->ctx.handle ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + RETURN_TRUE; +} + + +// 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 ); + + zval* stmt_r = NULL; + sqlsrv_stmt* stmt = NULL; + + RETVAL_TRUE; + + DECL_FUNC_NAME( "sqlsrv_free_stmt" ); + LOG_FUNCTION; + + // 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 ); + + // take only the statement resource + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ) == FAILURE ) { + handle_error( NULL, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + if( Z_TYPE_P( stmt_r ) == IS_NULL ) { + RETURN_TRUE; + } + else { + handle_error( NULL, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + } + + stmt = static_cast( zend_fetch_resource( &stmt_r TSRMLS_CC, -1, "sqlsrv_stmt", NULL, 1, sqlsrv_stmt::descriptor )); + if( stmt == NULL ) { + handle_error( NULL, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + + free_odbc_resources( stmt TSRMLS_CC ); + + // this frees up the php resources as well + remove_from_connection( stmt 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( stmt_r )); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_STMT, "Failed to remove stmt resource %d", Z_RESVAL_P( stmt_r )); + } + ZVAL_NULL( stmt_r ); + + RETURN_TRUE; +} + + + +// 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 ) +{ + sqlsrv_stmt* stmt = NULL; + bool executed = false; + + DECL_FUNC_NAME( "sqlsrv_execute" ); + LOG_FUNCTION; + + // take only the statement resource + PROCESS_PARAMS( stmt, _FN_, "r" ); + + CHECK_SQL_ERROR_EX( stmt->prepared == false, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_PREPARED, RETURN_FALSE ); + + // reset the parameter currently sending + stmt->current_parameter = NULL; + + // execute the prepared statement (false means use SQLExecute rather than SQLExecDirect + executed = sqlsrv_stmt_common_execute( stmt, NULL, 0, false, _FN_ TSRMLS_CC ); + if( !executed ) { + RETURN_FALSE; + } + + RETURN_TRUE; +} + + +// 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stmt* stmt = NULL; + SQLSMALLINT num_cols; + + DECL_FUNC_NAME( "sqlsrv_fetch" ); + LOG_FUNCTION; + + // take only the statement resource + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // make sure the statement has been executed + CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE ); + CHECK_SQL_ERROR_EX( stmt->past_fetch_end, stmt, _FN_, SQLSRV_ERROR_FETCH_PAST_END, RETURN_FALSE ); + + r = SQLNumResultCols( stmt->ctx.handle, &num_cols ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + CHECK_SQL_ERROR_EX( num_cols == 0, stmt, _FN_, SQLSRV_ERROR_NO_FIELDS, RETURN_FALSE ); + + close_active_stream( stmt TSRMLS_CC ); + + // move to the next record + r = SQLFetch( stmt->ctx.handle ); + // return Zend NULL if we're at the end of the result set. + if( r == SQL_NO_DATA ) { + stmt->past_fetch_end = true; + RETURN_NULL(); + } + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // 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; + + RETURN_TRUE; +} + + +// 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 ) +{ + sqlsrv_stmt* stmt = NULL; + int fetch_type = SQLSRV_FETCH_BOTH; + + DECL_FUNC_NAME( "sqlsrv_fetch_array" ); + LOG_FUNCTION; + + // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE) + PROCESS_PARAMS( stmt, _FN_, "r|l", &fetch_type ); + + // retrieve the hash table directly into the return_value variable for return. Any errors + // are handled directly in fetch_common. + fetch_common( stmt, fetch_type, return_value, _FN_, true TSRMLS_CC ); +} + + +// 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 (SQL Server 2005 Driver for PHP). +// +// 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 ) +{ + sqlsrv_stmt* stmt = NULL; + int zr = SUCCESS; + // stdClass is the name of the system's default base class in PHP + emalloc_auto_ptr stdclass_name; + stdclass_name = estrdup( STDCLASS_NAME ); + char* class_name = stdclass_name; + int class_name_len = STDCLASS_NAME_LEN; + zval* ctor_params_z = NULL; + + DECL_FUNC_NAME( "sqlsrv_fetch_object" ); + LOG_FUNCTION; + + // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE) + PROCESS_PARAMS( stmt, _FN_, "r|sa", &class_name, &class_name_len, &ctor_params_z ); + + // fetch the fields into an associative hash table + fetch_common( stmt, SQLSRV_FETCH_ASSOC, return_value, _FN_, false TSRMLS_CC ); + if( Z_TYPE_P( return_value ) != IS_ARRAY ) { + return; + } + HashTable* 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; + zend_str_tolower( class_name, class_name_len ); + zr = zend_lookup_class( class_name, class_name_len, &class_entry TSRMLS_CC ); + if( zr == FAILURE ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_ZEND_BAD_CLASS TSRMLS_CC, class_name ); + zend_hash_destroy( Z_ARRVAL_P( return_value )); + FREE_HASHTABLE( Z_ARRVAL_P( return_value )); + RETURN_FALSE; + } + + // create the instance of the object + // 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 ); + if( zr == FAILURE ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_ZEND_OBJECT_FAILED TSRMLS_CC, class_name ); + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + RETURN_FALSE; + } + + // merge in the "properties" (associative array) returned from the fetch doing this vice versa + // (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 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 + emalloc_auto_ptr params_m; + zval_auto_ptr ctor_retval_z; + ALLOC_INIT_ZVAL( ctor_retval_z ); + int num_params = 0; + if( ctor_params_z != NULL ) { + HashTable* ctorp_ht = Z_ARRVAL_P( ctor_params_z ); + num_params = zend_hash_num_elements( ctorp_ht ); + params_m = reinterpret_cast( emalloc( num_params * sizeof( zval**) )); + + int i; + for( i = 0, zend_hash_internal_pointer_reset( ctorp_ht ); + zend_hash_has_more_elements( ctorp_ht ) == SUCCESS; + zend_hash_move_forward( ctorp_ht ), ++i ) { + + zr = zend_hash_get_current_data_ex( ctorp_ht, reinterpret_cast(&(params_m[ i ])), NULL ); + if( zr == FAILURE ) { + zval_ptr_dtor( &return_value ); + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_ZEND_OBJECT_FAILED TSRMLS_CC, class_name ); + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + RETURN_FALSE; + } + } + } + + // 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. + fci.object_pp = &return_value; + memset( &fcic, 0, sizeof( fcic )); + fcic.initialized = 1; + fcic.function_handler = (*class_entry)->constructor; + fcic.calling_scope = *class_entry; + fcic.object_pp = &return_value; + zr = zend_call_function( &fci, &fcic TSRMLS_CC ); + if( zr == FAILURE ) { + zval_ptr_dtor( &return_value ); + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_ZEND_OBJECT_FAILED TSRMLS_CC, class_name ); + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + RETURN_FALSE; + } + } +} + + +// 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + int zr = SUCCESS; + sqlsrv_stmt* stmt = NULL; + SQLSMALLINT num_cols = -1; + SQLUSMALLINT field_name_len_max = USHRT_MAX; + SQLSMALLINT temp = 0; + emalloc_auto_ptr field_name_temp; + SQLSMALLINT field_name_len = -1; + SQLSMALLINT field_type = 0; + SQLULEN field_size = ULONG_MAX; + SQLSMALLINT field_scale = -1; + SQLSMALLINT field_is_nullable = 0; + + DECL_FUNC_NAME( "sqlsrv_field_metadata" ); + LOG_FUNCTION; + + // take the statement resource only + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // get the number of fields in the resultset + r = SQLNumResultCols( stmt->ctx.handle, &num_cols ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // get the maximum length of a field name + r = SQLGetInfo( stmt->conn->ctx.handle, SQL_MAX_COLUMN_NAME_LEN, &field_name_len_max, sizeof( field_name_len_max ), &temp ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + zval_auto_ptr result_meta_data; + ALLOC_INIT_ZVAL( result_meta_data ); + zr = array_init( result_meta_data ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + + for( SQLSMALLINT f = 1; f <= num_cols; ++f ) { + + field_name_temp = static_cast( emalloc( field_name_len_max + 1 )); + + // retrieve the field information + SQLSRV_STATIC_ASSERT( sizeof( char ) == sizeof( SQLCHAR )); + r = SQLDescribeCol( stmt->ctx.handle, f, reinterpret_cast( field_name_temp.get()), field_name_len_max, &field_name_len, + &field_type, &field_size, &field_scale, &field_is_nullable ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + zval_auto_ptr field_meta_data; + ALLOC_INIT_ZVAL( field_meta_data ); + zr = array_init( field_meta_data ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + + // add the name to the array + zr = add_assoc_string( field_meta_data, "Name", field_name_temp, 0 ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + field_name_temp.transferred(); + + // add the type to the array + zr = add_assoc_long( field_meta_data, "Type", field_type ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + + // depending on field type, we add the values into size or precision/scale and NULL out the other fields + switch( field_type ) { + case SQL_DECIMAL: + case SQL_NUMERIC: + zr = add_assoc_null( field_meta_data, "Size" ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zr = add_assoc_long( field_meta_data, "Precision", field_size ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zr = add_assoc_long( field_meta_data, "Scale", field_scale ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + 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: + zr = add_assoc_null( field_meta_data, "Size" ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zr = add_assoc_long( field_meta_data, "Precision", field_size ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zr = add_assoc_null( field_meta_data, "Scale" ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + break; + default: + zr = add_assoc_long( field_meta_data, "Size", field_size ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zr = add_assoc_null( field_meta_data, "Precision" ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zr = add_assoc_null( field_meta_data, "Scale" ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + break; + } + + // add the nullability to the array + zr = add_assoc_long( field_meta_data, "Nullable", field_is_nullable ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + + // add this field's meta data to the result set meta data + zr = add_next_index_zval( result_meta_data, field_meta_data ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + field_meta_data.transferred(); + } + + // return our built collection and transfer ownership + zval_ptr_dtor( &return_value ); + *return_value_ptr = result_meta_data; + result_meta_data.transferred(); +} + + +// 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 (SQL Server 2005 Driver for PHP). 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 ) +{ + SQLRETURN r; + sqlsrv_stmt* stmt = NULL; + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = PHPTYPE_INVALID; + long field_index = -1; + SQLLEN field_type = 0; + SQLLEN field_len = -1; + + DECL_FUNC_NAME( "sqlsrv_get_field" ); + LOG_FUNCTION; + + // get the statement, the field index, and the optional type to return it as + PROCESS_PARAMS( stmt, _FN_, "rl|l", &field_index, &sqlsrv_php_type ); + + // validate the field index is within range + SQLSMALLINT num_cols = 0; + r = SQLNumResultCols( stmt->ctx.handle, &num_cols ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + if( field_index < 0 || field_index >= num_cols ) { + handle_error( &stmt->ctx, current_log_subsystem, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + + // make sure the statement was actually executed and not just prepared + CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE ); + + // if they didn't specify a type as a parameter (so it still equals its initial value of PHPTYPE_INVALID) + if( sqlsrv_php_type.typeinfo.type == PHPTYPE_INVALID ) { + // get the SQL type of the field + r = SQLColAttribute( stmt->ctx.handle, static_cast( field_index + 1 ), SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &field_type ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + // also get its length (to separate var*(max) fields from normal var* fields + r = SQLColAttribute( stmt->ctx.handle, static_cast( field_index + 1 ), SQL_DESC_LENGTH, NULL, 0, NULL, &field_len ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // get the default type to return + sqlsrv_php_type = determine_sqlsrv_php_type( field_type, field_len, false ); + } + + // verify that we have an acceptable type to convert. + CHECK_SQL_ERROR_EX( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, _FN_, SQLSRV_ERROR_INVALID_TYPE, RETURN_FALSE ); + + // retrieve the data + get_field_common( stmt, _FN_, sqlsrv_php_type, static_cast( field_index ), &return_value TSRMLS_CC ); +} + + +// 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. +// +// 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stmt* stmt = NULL; + + DECL_FUNC_NAME( "sqlsrv_next_result" ); + LOG_FUNCTION; + + // get the statement resource + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // make sure that the statement has at least been executed + CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE ); + CHECK_SQL_ERROR_EX( stmt->past_next_result_end, stmt, _FN_, SQLSRV_ERROR_NEXT_RESULT_PAST_END, RETURN_FALSE ); + + close_active_stream( stmt TSRMLS_CC ); + + // call the ODBC API that does what we want + r = SQLMoreResults( stmt->ctx.handle ); + if( r == SQL_NO_DATA ) { + // if we're at the end, then return NULL + stmt->past_next_result_end = true; + RETURN_NULL(); + } + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + stmt->new_result_set(); + + RETURN_TRUE; +} + + +// 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stmt* stmt = NULL; + SQLSMALLINT fields = -1; + + DECL_FUNC_NAME( "sqlsrv_num_fields" ); + LOG_FUNCTION; + + // get the statement resource + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // retrieve the number of columns from ODBC + r = SQLNumResultCols( stmt->ctx.handle, &fields ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // return it to the script + RETURN_LONG( fields ); +} + + +// 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stmt* stmt = NULL; + SQLINTEGER rows = -1; + + DECL_FUNC_NAME( "sqlsrv_rows_affected" ); + LOG_FUNCTION; + + // get the statement resource + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // make sure it was executed + CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE ); + + // get the row count from ODBC... + r = SQLRowCount( stmt->ctx.handle, &rows ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // and return it + RETURN_LONG( rows ); +} + + + +// 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; + + DECL_FUNC_NAME( "sqlsrv_send_stream_data" ); + LOG_FUNCTION; + + // get the statement resource that we've bound streams to + PROCESS_PARAMS( stmt, _FN_, "r" ); + + // if everything was sent at execute time, just return that there is nothing more to send. + if( stmt->send_at_exec ) { + RETURN_NULL(); + } + + // send the next packet. The return_value parameter will be set to whatever the result was, + // so we just forward that as is. + send_stream_packet( stmt, return_value, _FN_ TSRMLS_CC ); +} + + +// ** 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 ); +} + + +// *** helper functions *** +// These are functions that deal with statement objects but are called from other modules, such as connection (conn.cpp) + +bool sqlsrv_stmt_common_execute( sqlsrv_stmt* stmt, const SQLCHAR* sql_string, int /*sql_len*/, bool direct, const char* _FN_ TSRMLS_DC ) +{ + SQLRETURN r; + SQLSMALLINT i; + emalloc_auto_ptr ind_ptr; + + close_active_stream( stmt TSRMLS_CC ); + + if( stmt->executed ) { + do { + r = SQLMoreResults( stmt->ctx.handle ); + CHECK_SQL_ERROR_EX( r == SQL_ERROR, stmt, _FN_, NULL, return false; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + } while( r != SQL_NO_DATA ); + } + + stmt->executed = false; + + if( stmt->params_z ) { + + + HashTable* params_ht = Z_ARRVAL_P( stmt->params_z ); + + ind_ptr = static_cast( emalloc( zend_hash_num_elements( params_ht ) * sizeof( SQLINTEGER ))); + + 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 ) { + + zval** param_zz = NULL; + zval *param_z = NULL; + int success = FAILURE; + SQLSMALLINT sql_c_type = SQL_C_BINARY; + SQLUINTEGER column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLPOINTER buffer = NULL; + SQLUINTEGER buffer_len = 0; + sqlsrv_sqltype sql_type; + sql_type.typeinfo.type = SQL_BINARY; + sql_type.typeinfo.size = SQLSRV_INVALID_SIZE; + sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE; + int direction = SQL_PARAM_INPUT; + zend_uchar php_type = PHPTYPE_INVALID; + + success = zend_hash_get_current_data( params_ht, (void**) ¶m_zz ); + CHECK_SQL_ERROR_EX( success == FAILURE, stmt, _FN_, SQLSRV_ERROR_VAR_REQUIRED, return false; ); + param_z = *static_cast( param_zz ); + + // if the user gave us a parameter array + if( Z_TYPE_P( param_z ) == IS_ARRAY ) { + + param_z = parse_param_array( stmt, _FN_, param_z, i, direction, php_type, sql_c_type, sql_type, + column_size, decimal_digits TSRMLS_CC ); + // an error occurred, so return false + if( param_z == NULL ) { + return false; + } + } + // otherwise use the defaults + else { + + bool success = determine_param_defaults( stmt, _FN_, param_z, i, php_type, direction, sql_type, + sql_c_type, column_size, decimal_digits TSRMLS_CC ); + if( !success ) { + return false; + } + } + + switch( php_type ) { + + case IS_NULL: + CHECK_SQL_ERROR_EX( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT, stmt, _FN_, + SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); + buffer = NULL; + buffer_len = 0; + ind_ptr[ i-1 ] = SQL_NULL_DATA; + break; + case IS_LONG: + buffer = ¶m_z->value; + buffer_len = sizeof( param_z->value.lval ); + ind_ptr[ i-1 ] = buffer_len; + break; + case IS_DOUBLE: + buffer = ¶m_z->value; + buffer_len = sizeof( param_z->value.dval ); + ind_ptr[ i-1 ] = buffer_len; + break; + case IS_STRING: + buffer = Z_STRVAL_PP( ¶m_z ); + buffer_len = Z_STRLEN_PP( ¶m_z ); + ind_ptr[ i-1 ] = buffer_len; + // resize the buffer to match the column size if it's smaller + // than the buffer given already + if(( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT ) && buffer_len < column_size ) { + buffer = static_cast( erealloc( buffer, column_size + 1 )); + } + break; + case IS_OBJECT: + { + char* class_name; + zend_uint class_name_len; + zval_auto_ptr function_z; + zval_auto_ptr buffer_z; + zval_auto_ptr format_z; + zval* params[2]; + int result; + + // verify this is a DateTime object + if( zend_get_object_classname( param_z, &class_name, &class_name_len TSRMLS_CC ) == FAILURE ) { + + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, i ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return false; + } + if( class_name_len != DATETIME_CLASS_NAME_LEN || stricmp( class_name, DATETIME_CLASS_NAME )) { + + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, i ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return false; + } + CHECK_SQL_ERROR_EX( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT, stmt, _FN_, + SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); + // call the PHP function date_format to convert the object to a string that SQL Server understands + ALLOC_INIT_ZVAL( buffer_z ); + ALLOC_INIT_ZVAL( function_z ); + ALLOC_INIT_ZVAL( format_z ); + ZVAL_STRINGL( function_z, "date_format", sizeof( "date_format" ) - 1, 1 ); + ZVAL_STRINGL( format_z, const_cast( DATETIME_FORMAT ), DATETIME_FORMAT_LEN, 1 ); + params[0] = param_z; + params[1] = format_z; + result = call_user_function( EG( function_table ), NULL, function_z, buffer_z, 2, params TSRMLS_CC ); + if( result == FAILURE ) { + + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, i ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + zval_ptr_dtor( &buffer_z ); + return false; + } + buffer = Z_STRVAL_P( buffer_z ); + // save the buffer we allocated for the date time string conversion + if( stmt->param_datetime_buffers == NULL ) { + ALLOC_INIT_ZVAL( stmt->param_datetime_buffers ); + int zr = array_init( stmt->param_datetime_buffers ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false ); + } + int zr = add_next_index_zval( stmt->param_datetime_buffers, buffer_z ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, return false ); + buffer_len = Z_STRLEN_P( buffer_z ); + buffer_z.transferred(); + ind_ptr[ i-1 ] = buffer_len; + break; + } + case IS_RESOURCE: + { + CHECK_SQL_ERROR_EX( direction == SQL_PARAM_INPUT_OUTPUT || direction == SQL_PARAM_OUTPUT, stmt, _FN_, + SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); + php_stream* param_stream = NULL; + php_stream_from_zval_no_verify( param_stream, ¶m_z ); + buffer = param_z; + zval_add_ref( ¶m_z ); // so that it doesn't go away while we're using it + buffer_len = 0; + ind_ptr[ i-1 ] = SQL_DATA_AT_EXEC; + break; + } + default: + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAM_TYPE TSRMLS_CC ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return false; + } + + if( direction < 0 || direction > 0xffff ) DIE( "direction not valid SQLSMALLINT" ); + r = SQLBindParameter( stmt->ctx.handle, i, static_cast( direction ), sql_c_type, sql_type.typeinfo.type, column_size, decimal_digits, + buffer, buffer_len, &ind_ptr[ i-1 ] ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + } + } + + // execute the statement + if( direct ) { + if( sql_string == NULL ) DIE( "sqlsrv_stmt_common_execute: sql_string must be valid when direct = true"); + r = SQLExecDirect( stmt->ctx.handle, const_cast( sql_string ), SQL_NTS /*sql_len*/ ); + } + else { + if( sql_string != NULL ) DIE( "sqlsrv_stmt_common_execute: sql_string must be NULL when direct = false"); + r = SQLExecute( stmt->ctx.handle ); + } + if( r != SQL_NEED_DATA ) { + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); return false; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + } + else if( stmt->send_at_exec == true ) { + + zval return_value; + while( send_stream_packet( stmt, &return_value, _FN_ TSRMLS_CC )) { } + if( Z_TYPE( return_value ) != IS_NULL ) { + return false; + } + } + + // false means to not release the datetime buffers we just allocated. + stmt->new_result_set( false ); + stmt->executed = true; + + return true; +} + +void free_odbc_resources( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + SQLRETURN r = SQL_SUCCESS; + + // release any cached field data we had + stmt->new_result_set(); + + // release a parameter array if we had one + if( stmt->params_z ) { + zval_ptr_dtor( &stmt->params_z ); + stmt->params_z = NULL; + } + + close_active_stream( stmt TSRMLS_CC ); + + if( stmt->param_buffer != NULL ) { + efree( stmt->param_buffer ); + stmt->param_buffer = NULL; + } + + r = SQLFreeHandle( SQL_HANDLE_STMT, stmt->ctx.handle ); + + // we don't check errors here because the error log may have already gone away. We just log them. + if( !SQL_SUCCEEDED( r ) ) { + LOG( SEV_ERROR, LOG_STMT, "Failed to free handle for stmt resource %d", stmt->conn_index ); + } + + // mark the statement as closed + stmt->ctx.handle = SQL_NULL_HANDLE; +} + +void free_php_resources( zval* stmt_z TSRMLS_DC ) +{ + sqlsrv_stmt* stmt = NULL; + + stmt = static_cast( zend_fetch_resource( &stmt_z TSRMLS_CC, -1, "sqlsrv_stmt", NULL, 1, sqlsrv_stmt::descriptor )); + if( stmt == NULL ) { + LOG( SEV_WARNING, current_log_subsystem, "Statement resource %1!d! already released", Z_RESVAL_P( stmt_z )); + return; + } + + // cause any variables still holding a reference to this to be invalid so + // they cause an error when passed to a sqlsrv function. If the removal fails, + // we log it. + int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_z )); + if( zr == FAILURE ) { + LOG( SEV_ERROR, LOG_STMT, "Failed to remove stmt resource %d", Z_RESVAL_P( stmt_z )); + } + + ZVAL_NULL( stmt_z ); + efree( stmt ); + zval_ptr_dtor( &stmt_z ); +} + +void remove_from_connection( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + if( stmt->ctx.handle != NULL ) DIE( "Statement ODBC resources not released prior to removing from the connection" ); + + // delete the entry in the connection's statement list + LOG( SEV_NOTICE, LOG_STMT, "Deleting statement index %1!d! from the connection", stmt->conn_index ); + if( zend_hash_index_del( stmt->conn->stmts, stmt->conn_index ) == FAILURE ) { + DIE( "Couldn't delete statement index %d from the connection", stmt->conn_index ); + } + + // set the connection to released + stmt->conn = NULL; +} + +// sqlsrv_stmt_hash_dtor +// called when the entry in the connection's list of statements is deleted + +void sqlsrv_stmt_hash_dtor( void* stmt_ptr ) +{ + zval* stmt_z = *(static_cast( stmt_ptr )); + + if( stmt_z->refcount <= 0 ) { + DIE( "Statement refcount should be > 0 when deleting from the connection's statement list" ); + } + + sqlsrv_stmt* stmt = NULL; + + TSRMLS_FETCH(); + + stmt = static_cast( zend_fetch_resource( &stmt_z TSRMLS_CC, -1, "sqlsrv_stmt", NULL, 1, sqlsrv_stmt::descriptor )); + if( !stmt ) + return; + + stmt->conn = NULL; // remove the connection so the statement resource destructor won't try to remove itself from the connection's list + + if( stmt->ctx.handle != SQL_NULL_HANDLE ) { + free_odbc_resources( stmt TSRMLS_CC ); + } + + free_php_resources( stmt_z TSRMLS_CC ); +} + +void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ) +{ + // get the structure + sqlsrv_stmt *stmt = static_cast( rsrc->ptr ); + LOG( SEV_NOTICE, LOG_STMT, "sqlsrv_stmt_dtor: entering" ); + + if( stmt->ctx.handle != SQL_NULL_HANDLE ) { + free_odbc_resources( stmt TSRMLS_CC ); + } + + if( stmt->conn ) { + remove_from_connection( stmt TSRMLS_CC ); + } +} + + +// 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( bool release_datetime_buffers ) +{ + fetch_called = false; + if( fetch_fields ) { + for( int i = 0; i < fetch_fields_count; ++i ) { + efree( fetch_fields[ i ].name ); + } + efree( fetch_fields ); + } + fetch_fields = NULL; + fetch_fields_count = 0; + last_field_index = -1; + past_fetch_end = false; + past_next_result_end = false; + + // if we allocated any datetime strings, release them here. + if( param_datetime_buffers && release_datetime_buffers ) { + zval_ptr_dtor( ¶m_datetime_buffers ); + param_datetime_buffers = NULL; + } +} + +// *** internal functions *** + +namespace { + +// check_for_next_stream_parameter +// check for the next stream parameter. Returns true if another parameter is ready, false if either an error +// or there are no more parameters. +bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt, __out zval* return_value TSRMLS_DC ) +{ + zval* param_z = NULL; + SQLRETURN r = SQL_SUCCESS; + + RETVAL_TRUE; + + r = SQLParamData( stmt->ctx.handle, reinterpret_cast( ¶m_z )); + // if there is a waiting parameter, make it current + if( r == SQL_NEED_DATA ) { + stmt->current_parameter = param_z; + stmt->current_parameter_read = 0; + } + // otherwise if it wasn't an error, we've exhausted the bound parameters, so return that we're done + else if( SQL_SUCCEEDED( r )) { + CHECK_SQL_WARNING( r, stmt, "sqlsrv_send_stream_data", NULL ); + RETVAL_NULL(); + return false; + } + // otherwise, record the error and return false + else { + CHECK_SQL_ERROR( r, stmt, "sqlsrv_send_stream_data", NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; ); + } + + // there are more parameters + return true; +} + + +// get_field_common +// common code shared between sqlsrv_get_field and sqlsrv_fetch_array. The "return value" is transferred via the field_value +// parameter, FALSE being when an error occurs. +void get_field_common( __inout sqlsrv_stmt* stmt, const char* _FN_, sqlsrv_phptype sqlsrv_phptype, SQLUSMALLINT field_index, __out zval**field_value TSRMLS_DC ) +{ + SQLRETURN r; + + close_active_stream( stmt TSRMLS_CC ); + + // make sure that fetch is called before trying to retrieve to return a helpful sqlsrv error + CHECK_SQL_ERROR_EX( !stmt->fetch_called, stmt, _FN_, SQLSRV_ERROR_FETCH_NOT_CALLED, ZVAL_FALSE( *field_value ); return; ); + + // make sure they're not trying to retrieve fields incorrectly. Otherwise return a helpful sqlsrv error + if( stmt->last_field_index > field_index ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_FIELD_INDEX_ERROR TSRMLS_CC, field_index, stmt->last_field_index ); + ZVAL_FALSE( *field_value ); + return; + } + + // what we do is based on the PHP type to be returned. + switch( sqlsrv_phptype.typeinfo.type ) { + + // call a refactored routine get_field_as_string + case SQLSRV_PHPTYPE_STRING: + { + SQLSMALLINT c_type = ( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_CHAR ) ? SQL_C_CHAR : SQL_C_BINARY; + get_field_as_string( stmt, c_type, field_index, *field_value, _FN_ TSRMLS_CC ); + if( Z_TYPE_PP( field_value ) == IS_BOOL && Z_LVAL_PP( field_value ) == 0 ) { + return; + } + } + 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: + { + php_stream* stream; + sqlsrv_stream* ss; + SQLINTEGER sql_type; + SQLINTEGER sql_display_size; + + r = SQLColAttribute( stmt->ctx.handle, field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + r = SQLColAttribute( stmt->ctx.handle, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &sql_display_size ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + CHECK_SQL_ERROR_EX( !is_streamable_type( sql_type ), stmt, _FN_, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, ZVAL_FALSE( *field_value ); return; ); + + + stream = php_stream_open_wrapper( "sqlsrv://teststuff", "r", 0, NULL ); + CHECK_SQL_ERROR_EX( !stream, stmt, _FN_, SQLSRV_ERROR_STREAM_CREATE, ZVAL_FALSE( *field_value ); return; ); + ss = static_cast( stream->abstract ); + ss->stmt = stmt; + ss->field = field_index; + if( sql_type > SHRT_MAX || sql_type < SHRT_MIN ) DIE( "sql_type out of range for short integer. SQLColAttribute probably returned a bad result." ); + ss->sql_type = static_cast( sql_type ); + ss->encoding = sqlsrv_phptype.typeinfo.encoding; + // turn our stream into a zval to be returned + php_stream_to_zval( stream, *field_value ); + zval_add_ref( field_value ); // this is released in sqlsrv_stream_close + // mark this as our active stream + stmt->active_stream = *field_value; + } + break; + + // Get the integer from SQLGetData and return it in a zval + case SQLSRV_PHPTYPE_INT: + { + SQLRETURN r; + SQLINTEGER field_len; + long value; + r = SQLGetData( stmt->ctx.handle, field_index + 1, SQL_C_LONG, &value, 0, &field_len ); + if( r == SQL_NO_DATA ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index ); + ZVAL_FALSE( *field_value ); + return; + } + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + // if the integer field was NULL, return a zval NULL + if( field_len == SQL_NULL_DATA ) { + ZVAL_NULL( *field_value ); + break; + } + ZVAL_LONG( *field_value, value ); + break; + } + + // Get the double from SQLGetData and return it in a zval + case SQLSRV_PHPTYPE_FLOAT: + { + SQLRETURN r; + SQLINTEGER field_len; + double value; + r = SQLGetData( stmt->ctx.handle, field_index + 1, SQL_C_DOUBLE, &value, 0, &field_len ); + if( r == SQL_NO_DATA ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index ); + ZVAL_FALSE( *field_value ); + return; + } + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + // if the double was a NULL, return a zval NULL + if( field_len == SQL_NULL_DATA ) { + ZVAL_NULL( *field_value ); + break; + } + ZVAL_DOUBLE( *field_value, value ); + 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: + { + SQLRETURN r; + SQLINTEGER field_len; + char value[ MAX_DATETIME_STRING_LEN ]; + zval_auto_ptr value_z; + zval_auto_ptr function_z; + zval* params[1]; + + ALLOC_INIT_ZVAL( value_z ); + ALLOC_INIT_ZVAL( function_z ); + ZVAL_STRINGL( function_z, "date_create", sizeof( "date_create" ) - 1, 1 ); + + r = SQLGetData( stmt->ctx.handle, field_index + 1, SQL_C_CHAR, value, MAX_DATETIME_STRING_LEN, &field_len ); + if( r == SQL_NO_DATA ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index ); + ZVAL_FALSE( *field_value ); + return; + } + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, ZVAL_FALSE( *field_value ); return; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + if( field_len == SQL_NULL_DATA ) { + ZVAL_NULL( *field_value ); + break; + } + ZVAL_STRINGL( value_z, value, field_len, 1 ); + params[0] = value_z; + // to convert the string date to a DateTime object, we call the "date_create" PHP function + if( call_user_function( EG( function_table ), NULL, function_z, *field_value, 1, params TSRMLS_CC ) == FAILURE ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED TSRMLS_CC ); + ZVAL_FALSE( *field_value ); + } + else { + stmt->last_field_index = field_index; + } + + return; + } + + // an unknown type was passed in. This should have been caught before reaching here. + default: + DIE( "Shouldn't reach here with an invalid type. Should have been caught before." ); + break; + } + + // record the last field was the one retrieved. + stmt->last_field_index = field_index; +} + + +// returns the ODBC C type constant that matches the PHP type and encoding given +// SQLTYPE_INVALID is returned when an invalid type is given when nothing matches +SQLSMALLINT determine_c_type( int php_type, int encoding ) +{ + SQLSMALLINT sql_c_type = SQLTYPE_INVALID; + + switch( php_type ) { + + case IS_NULL: + sql_c_type = SQL_C_CHAR; + break; + 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: + if( encoding == SQLSRV_ENCODING_CHAR ) { + sql_c_type = SQL_C_CHAR; + } + else if( encoding == SQLSRV_ENCODING_BINARY ) { + sql_c_type = SQL_C_BINARY; + } + else { + sql_c_type = SQLTYPE_INVALID; + } + 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 sqlsrv_common_execute. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + sql_c_type = SQL_C_CHAR; + break; + default: + sql_c_type = SQLTYPE_INVALID; + break; + } + + return sql_c_type; +} + +// returns the SQL type constant that matches the PHP type and encoding given +// SQLTYPE_INVALID is returned when an invalid type is given since no SQL constant matches +sqlsrv_sqltype determine_sql_type( int php_type, int encoding, zval const* value ) +{ + sqlsrv_sqltype sql_type; + sql_type.typeinfo.type = SQLTYPE_INVALID; + sql_type.typeinfo.size = SQLSRV_INVALID_SIZE; + sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE; + + switch( php_type ) { + + case IS_NULL: + sql_type.typeinfo.type = SQL_CHAR; + sql_type.typeinfo.size = 1; + break; + case IS_LONG: + sql_type.typeinfo.type = SQL_INTEGER; + break; + case IS_DOUBLE: + sql_type.typeinfo.type = SQL_FLOAT; + break; + case IS_RESOURCE: + case IS_STRING: + if( encoding == SQLSRV_ENCODING_CHAR ) { + sql_type.typeinfo.type = SQL_VARCHAR; + } + else { + sql_type.typeinfo.type = SQL_VARBINARY; + } + if( Z_STRLEN_P( value ) > SQL_SERVER_2005_MAX_FIELD_SIZE ) { + sql_type.typeinfo.size = SQLSRV_SIZE_MAX_TYPE; + } + else { + sql_type.typeinfo.size = Z_STRLEN_P( value ); // TODO: this might need -1 added. + } + 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 sqlsrv_common_execute. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + sql_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + break; + default: + // this comes from the user, so we can't assert here + sql_type.typeinfo.type = SQLTYPE_INVALID; + break; + } + + return sql_type; +} + +// given a SQL Server type, return a sqlsrv php type +sqlsrv_phptype determine_sqlsrv_php_type( 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 = SQLSRV_ENCODING_CHAR; + 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 = SQLSRV_ENCODING_CHAR; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + } + 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 = SQLSRV_ENCODING_CHAR; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + } + break; + case SQL_FLOAT: + case SQL_REAL: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + break; + case SQL_TYPE_TIMESTAMP: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + break; + default: + sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; + break; + } + + return sqlsrv_phptype; +} + +// 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_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_WCHAR: + case SQL_WVARCHAR: + 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_2005_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; + case SQL_TYPE_TIMESTAMP: + *column_size = 23; + *decimal_digits = 3; + 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; +} + +// 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: + break; + default: + 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 ) { + return true; + } + break; + } + } + + return false; +} + +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; +} + +SQLSMALLINT binary_or_char_encoding( SQLSMALLINT c_type ) +{ + switch( c_type ) { + + case SQL_C_BINARY: + return SQLSRV_ENCODING_BINARY; + // we return character encoding for LONG and DOUBLE as well since by default the encoding is always character, + // even though it won't mean anything for these data types. + case SQL_C_CHAR: + case SQL_C_LONG: + case SQL_C_DOUBLE: + break; + default: + DIE( "Invalid c_type in binary_or_char_encoding" ); + }; + + return SQLSRV_ENCODING_CHAR; +} + +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; +} + +// 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; + } + + // fetch the stream + php_stream* stream; + // 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 ); + if( stream == NULL ) { + DIE( "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. + if( stmt->active_stream != NULL ) { + DIE( "Active stream not closed." ); + } +} + +// a refactoring since the clause in get_field_common was becoming too large. +void get_field_as_string( sqlsrv_stmt const* s, SQLSMALLINT c_type, SQLUSMALLINT field_index, __out zval* return_value, const char* _FN_ TSRMLS_DC ) +{ + SQLRETURN r; + char* field; + SQLINTEGER field_len; + SQLUINTEGER sql_display_size; + SQLINTEGER sql_type; + int initial_field_len = INITIAL_FIELD_STRING_LEN + (( c_type == SQL_C_CHAR ) ? 1 : 0); + + if( c_type != SQL_C_CHAR && c_type != SQL_C_BINARY ) DIE( "get_field_as_string requires C type to be either SQL_C_CHAR or SQL_C_BINARY" ); + + r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &sql_display_size ); + CHECK_SQL_ERROR( r, s, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, s, _FN_, NULL ); + r = SQLColAttribute( s->ctx.handle, field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR( r, s, _FN_, NULL, RETURN_FALSE; ); + CHECK_SQL_WARNING( r, s, _FN_, NULL ); + // 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 = initial_field_len; + field = static_cast( emalloc( field_len + 1 )); + r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field, field_len, &field_len ); + if( field_len == SQL_NULL_DATA ) { + efree( field ); + RETURN_NULL(); + } + if( r == SQL_NO_DATA ) { + handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index ); + efree( field ); + RETURN_FALSE; + return; + } + if( r == SQL_SUCCESS_WITH_INFO ) { + SQLRETURN r; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + r = SQLGetDiagField( SQL_HANDLE_STMT, s->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); + if( is_truncated_warning( state ) ) { + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // we do a power of two increasing size allocation to retrieve all the contents + if( field_len == SQL_NO_TOTAL ) { + SQLINTEGER dummy_field_len; + field_len = initial_field_len - ((c_type == SQL_C_CHAR) ? 1 : 0); + do { + initial_field_len = field_len; + field_len *= 2; + field = static_cast( erealloc( field, field_len + 1 )); + field_len -= initial_field_len; + r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field + initial_field_len, + field_len + ((c_type == SQL_C_CHAR ) ? 1 : 0), &dummy_field_len ); + + // 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 += dummy_field_len; + else + field_len += initial_field_len; + if( r == SQL_SUCCESS_WITH_INFO ) { + SQLGetDiagField( SQL_HANDLE_STMT, s->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, 6, &len ); + } + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; ); + } + else { + field = static_cast( erealloc( field, field_len + 1 )); + field_len -= INITIAL_FIELD_STRING_LEN; + r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field + INITIAL_FIELD_STRING_LEN, + field_len + (( c_type == SQL_C_CHAR ) ? 1 : 0), &field_len ); + if( field_len == SQL_NULL_DATA ) { + efree( field ); + RETURN_NULL(); + } + if( r == SQL_NO_DATA ) { + handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index ); + efree( field ); + RETURN_FALSE; + return; + } + CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; ); + field_len += INITIAL_FIELD_STRING_LEN; + field[ field_len ] = '\0'; // NULL terminate the string + } + } + else { + handle_warning( &s->ctx, LOG_STMT, _FN_, NULL TSRMLS_CC ); + } + } + else { + CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; ); + } + } + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_2005_MAX_FIELD_SIZE ) { + // only allow binary retrievals for char and binary types. All others get a char type automatically. + if( is_fixed_size_type( sql_type )) { + c_type = SQL_C_CHAR; + } + + if( c_type == SQL_C_BINARY && ( sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR )) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); // include the null terminator + } + else { + ++sql_display_size; + } + field = static_cast( emalloc( sql_display_size + 1 )); + // get the data + r = SQLGetData( s->ctx.handle, field_index + 1, c_type, field, sql_display_size, &field_len ); + if( field_len == SQL_NULL_DATA ) { + efree( field ); + RETURN_NULL(); + } + if( r == SQL_NO_DATA ) { + handle_error( &s->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_NO_DATA TSRMLS_CC, field_index ); + efree( field ); + RETURN_FALSE; + return; + } + CHECK_SQL_ERROR( r, s, _FN_, NULL, efree( field ); RETURN_FALSE; ); + CHECK_SQL_WARNING( r, s, _FN_, NULL ); + } + else { + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } + + field[ field_len ] = '\0'; // prevent a warning in debug mode + ZVAL_STRINGL( return_value, field, field_len, 0 ); + return; +} + + +// 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; + int size_len; + long size; + + 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_2005_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, LOG_STMT, "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 = SQL_SERVER_2005_DEFAULT_PRECISION; + long scale = SQL_SERVER_2005_DEFAULT_SCALE; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { + + return; + } + + if( prec > SQL_SERVER_2005_MAX_PRECISION ) { + LOG( SEV_ERROR, LOG_STMT, "Invalid precision. Precision can't be > 38" ); + prec = SQLSRV_INVALID_PRECISION; + } + + if( prec < 0 ) { + LOG( SEV_ERROR, LOG_STMT, "Invalid precision. Precision can't be negative" ); + prec = SQLSRV_INVALID_PRECISION; + } + + if( scale > prec ) { + LOG( SEV_ERROR, LOG_STMT, "Invalid scale. Scale can't be > precision" ); + scale = SQLSRV_INVALID_PRECISION; + } + + 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 ); + + char* encoding_param; + sqlsrv_phptype sqlsrv_phptype; + sqlsrv_phptype.typeinfo.type = type; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + int encoding_param_len = 0; + + if( type != SQLSRV_PHPTYPE_STREAM && type != SQLSRV_PHPTYPE_STRING ) { + DIE( "Invalid type passed to type_and_encoding" ); + } + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { + return; + } + + if( !strnicmp( encoding_param, "binary", sizeof( "binary" ) / sizeof(char))) { + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + else if( !strnicmp( encoding_param, "char", sizeof( "char" ) / sizeof(char))) { + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + } + else { + LOG( SEV_ERROR, LOG_STMT, "Invalid encoding. Must be either \"binary\" (SQLSRV_ENC_BINARY) or \"char\" (SQLSRV_ENC_CHAR)" ); + } + + ZVAL_LONG( return_value, sqlsrv_phptype.value ); +} + + +// fetch_common +// the common code shared between fetch_array and fetch_object. This returns a hash_table into return_value +// containing the fields either indexed by number and/or field name as determined by fetch_type. +void fetch_common( __inout sqlsrv_stmt* stmt, int fetch_type, __out zval* return_value, const char* _FN_, bool allow_empty_field_names TSRMLS_DC ) +{ + SQLRETURN r = SQL_SUCCESS; + int zr = SUCCESS; + SQLSMALLINT num_cols = 0; + SQLUSMALLINT field_name_len_max = 0; + SQLSMALLINT unused = -1; + + // make sure that the fetch type is legal + CHECK_SQL_ERROR_EX( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH, stmt, _FN_, + SQLSRV_ERROR_INVALID_FETCH_TYPE, RETURN_FALSE ); + // make sure the statement has been executed + CHECK_SQL_ERROR_EX( !stmt->executed, stmt, _FN_, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, RETURN_FALSE ); + CHECK_SQL_ERROR_EX( stmt->past_fetch_end, stmt, _FN_, SQLSRV_ERROR_FETCH_PAST_END, RETURN_FALSE ); + + // get the numer of columns in the result set + r = SQLNumResultCols( stmt->ctx.handle, &num_cols ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + CHECK_SQL_ERROR_EX( num_cols == 0, stmt, _FN_, SQLSRV_ERROR_NO_FIELDS, RETURN_FALSE ); + + // get the maximum size for a field name + r = SQLGetInfo( stmt->conn->ctx.handle, SQL_MAX_COLUMN_NAME_LEN, &field_name_len_max, sizeof( field_name_len_max ), &unused ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // move to the next record + r = SQLFetch( stmt->ctx.handle ); + // return a Zend NULL if we're at the end of the result set. + if( r == SQL_NO_DATA ) { + stmt->past_fetch_end = true; + RETURN_NULL(); + } + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // make it legal to retrieve fields + stmt->fetch_called = true; + stmt->last_field_index = -1; + + zval_auto_ptr fields; + MAKE_STD_ZVAL( fields ); + zr = array_init( fields ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + + // 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_fields == NULL ) { + + char* field_name_temp = static_cast( alloca( field_name_len_max )); + SQLSMALLINT field_name_len; + emalloc_auto_ptr field_names; + field_names = static_cast( emalloc( num_cols * sizeof( sqlsrv_fetch_field ))); + + for( SQLUSMALLINT f = 0; f < num_cols; ++f ) { + r = SQLColAttribute( stmt->ctx.handle, f + 1, SQL_DESC_NAME, field_name_temp, field_name_len_max, &field_name_len, &unused ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + field_names[ f ].name = static_cast( emalloc( field_name_len + 1 )); + memcpy( (void*) field_names[ f ].name, field_name_temp, field_name_len ); + field_names[ f ].name[ field_name_len ] = '\0'; // null terminate the field name since SQLColAttribute doesn't. + field_names[ f ].len = field_name_len + 1; + } + stmt->fetch_fields = field_names; + stmt->fetch_fields_count = num_cols; + field_names.transferred(); + } + + for( SQLUSMALLINT f = 0; f < num_cols; ++f ) { + + SQLLEN field_type; + SQLLEN field_len; + sqlsrv_phptype sqlsrv_php_type; + // we don't use a zend_auto_ptr because ownership is transferred to the fields hash table + // that will be destroyed if an error occurs. + zval_auto_ptr field; + + MAKE_STD_ZVAL( field ); + + // get the field type and length so we can determine the approppriate PHP type to map this to. + r = SQLColAttribute( stmt->ctx.handle, f + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &field_type ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + r = SQLColAttribute( stmt->ctx.handle, f + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &field_len ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, RETURN_FALSE ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + + // map from the SQL Server type to a PHP type for the field + sqlsrv_php_type = determine_sqlsrv_php_type( field_type, field_len, true ); + if( sqlsrv_php_type.typeinfo.type == PHPTYPE_INVALID ) { DIE( "Couldn't understand type returned by ODBC" ); } + + // get the value + get_field_common( stmt, _FN_, sqlsrv_php_type, f, &field TSRMLS_CC ); + if( Z_TYPE_P( field ) == IS_BOOL && Z_LVAL_P( field ) == 0 ) { + RETURN_FALSE; + } + + // if the fetch type is numeric, add an integer key for this field + if( fetch_type & SQLSRV_FETCH_NUMERIC ) { + zr = add_next_index_zval( fields, field ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zval_add_ref( &field ); + } + + // if the fetch type is associative add the field with the field name as the key + // (unnamed fields are permitted if the flag is passed in) + if( fetch_type & SQLSRV_FETCH_ASSOC ) { + + CHECK_SQLSRV_WARNING( stmt->fetch_fields[ f ].len == 1 && !allow_empty_field_names, SQLSRV_WARNING_FIELD_NAME_EMPTY, RETURN_FALSE; ); + + if( stmt->fetch_fields[ f ].len > 1 || allow_empty_field_names ) { + zr = add_assoc_zval( fields, stmt->fetch_fields[ f ].name, field ); + CHECK_ZEND_ERROR( zr, SQLSRV_ERROR_ZEND_HASH, RETURN_FALSE ); + zval_add_ref( &field ); + } + } + } + + *return_value = *fields; + ZVAL_NULL( fields ); + zval_ptr_dtor( &fields ); + fields.transferred(); +} + +// 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 or an error occurred. return_value is what should be returned +// to the script if it is given. Any errors that occur are posted here. + +bool send_stream_packet( __inout sqlsrv_stmt* stmt, __out zval* return_value, char const* _FN_ 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_parameter == NULL ) { + if( check_for_next_stream_parameter( stmt, return_value TSRMLS_CC ) == false ) { + // done. return_value is already set + return false; + } + } + + // get the stream from the zval we bound + php_stream* param_stream = NULL; + php_stream_from_zval_no_verify( param_stream, &stmt->current_parameter ); + CHECK_SQL_ERROR_EX( param_stream == NULL, stmt, _FN_, SQLSRV_ERROR_ZEND_STREAM, + zval_ptr_dtor( &stmt->current_parameter ); + stmt->current_parameter = NULL; + stmt->current_parameter_read = 0; + SQLCancel( stmt->ctx.handle ); + RETVAL_FALSE; + return false;); + + // 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_parameter_read == 0 ) { + // send an empty string, which is what a 0 length does. + r = SQLPutData( stmt->ctx.handle, stmt->param_buffer, 0 ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + } + zval_ptr_dtor( &stmt->current_parameter ); + stmt->current_parameter = NULL; + stmt->current_parameter_read = 0; + } + // read the data from the stream, send it via SQLPutData and track how much we've sent. + else { + size_t read = php_stream_read( param_stream, static_cast( stmt->param_buffer ), stmt->param_buffer_size ); + stmt->current_parameter_read += read; + if( read > 0 ) { + r = SQLPutData( stmt->ctx.handle, stmt->param_buffer, read ); + CHECK_SQL_ERROR( r, stmt, _FN_, NULL, SQLCancel( stmt->ctx.handle ); RETVAL_FALSE; return false; ); + CHECK_SQL_WARNING( r, stmt, _FN_, NULL ); + } + } + + RETVAL_TRUE; + return true; +} + +zval* parse_param_array( sqlsrv_stmt const* stmt, const char* _FN_, const zval* param_array, SQLSMALLINT param_num, __out int& direction, __out zend_uchar& php_type, SQLSMALLINT& sql_c_type, __out sqlsrv_sqltype& sql_type, + __out SQLUINTEGER& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ) +{ + zval** var_or_val; + zval** temp; + + bool php_type_param_was_null = true; + bool sql_type_param_was_null = true; + + // handle the array parameters that contain the value/var, direction, php_type, sql_type + zend_hash_internal_pointer_reset( Z_ARRVAL_P( param_array ) ); + if( zend_hash_has_more_elements( Z_ARRVAL_P( param_array )) == FAILURE || zend_hash_get_current_data( Z_ARRVAL_P( param_array ), (void**) &var_or_val ) == FAILURE ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_VAR_REQUIRED TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + + // if the direction is included, then use what they gave, otherwise INPUT is assumed + if( zend_hash_move_forward( Z_ARRVAL_P( param_array )) == SUCCESS && + zend_hash_get_current_data( Z_ARRVAL_P( param_array ), (void**) &temp ) == SUCCESS && + Z_TYPE_PP( temp ) != IS_NULL ) { + + if( Z_TYPE_PP( temp ) != IS_LONG ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + direction = Z_LVAL_PP( temp ); + if( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + } + else { + direction = SQL_PARAM_INPUT; + } + + // use the PHP type given rather than the type built in, since this could be an output parameter. + // determine the C type to pass to SQLBindParameter + if( zend_hash_move_forward( Z_ARRVAL_P( param_array ) ) == SUCCESS && + zend_hash_get_current_data( Z_ARRVAL_P( param_array ), (void**) &temp ) == SUCCESS && + Z_TYPE_PP( temp ) != IS_NULL ) { + + php_type_param_was_null = false; + + sqlsrv_phptype sqlsrv_phptype; + int encoding; + sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + if( Z_TYPE_PP( temp ) != IS_LONG ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + sqlsrv_phptype.value = Z_LVAL_PP( temp ); + if( !is_valid_sqlsrv_phptype( sqlsrv_phptype )) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + // make sure the enums are 1:1 + SQLSRV_STATIC_ASSERT( MAX_SQLSRV_PHPTYPE == ( sizeof( sqlsrv_to_php_type ) / sizeof( zend_uchar )) ); + php_type = sqlsrv_to_php_type[ sqlsrv_phptype.typeinfo.type - 1 ]; + encoding = sqlsrv_phptype.typeinfo.encoding; + sql_c_type = determine_c_type( php_type, encoding ); + if( sql_c_type == SQLTYPE_INVALID ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + } + // use the PHP type to determine the C type for SQLBindParameter + else { + + php_type_param_was_null = true; + php_type = Z_TYPE_PP( var_or_val ); + sql_c_type = determine_c_type( php_type, SQLSRV_ENCODING_CHAR ); + if( sql_c_type == SQLTYPE_INVALID ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + } + + // get the server type, column size/precision and the decimal digits if provided + if( zend_hash_move_forward( Z_ARRVAL_P( param_array ) ) == SUCCESS && + zend_hash_get_current_data( Z_ARRVAL_P( param_array ), (void**) &temp ) == SUCCESS && + Z_TYPE_PP( temp ) != IS_NULL ) { + + sql_type_param_was_null = false; + + if( Z_TYPE_PP( temp ) != IS_LONG ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + }; + sql_type.value = Z_LVAL_PP( temp ); + // since the user supplied this type, make sure it's valid + if( !is_valid_sqlsrv_sqltype( sql_type )) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + if( determine_column_size_or_precision( sql_type, &column_size, &decimal_digits ) == false ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PRECISION TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + } + // else use the type of the variable to infer the sql type, using a default character encoding + // (the default encoding really should be something in the INI) + else { + sql_type_param_was_null = true; + sql_type = determine_sql_type( php_type, binary_or_char_encoding( sql_c_type ), *var_or_val); + if( sql_type.typeinfo.type == SQLTYPE_INVALID ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + if( determine_column_size_or_precision( sql_type, &column_size, &decimal_digits ) == false ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PRECISION TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + } + + // if the user has given us a value of NULL as an input parameter, then use the default types. + if( Z_TYPE_PP( var_or_val ) == IS_NULL && direction == SQL_PARAM_INPUT && php_type != IS_NULL ) { + + // if the user has given us a varbinary field, then set the c type to binary and the php type to NULL + if( sql_type.typeinfo.type == SQL_VARBINARY ) { + sql_c_type = SQL_C_BINARY; + php_type = IS_NULL; + return *var_or_val; + } + + // otherwise, just set the defaults as if they had not given us a full parameter array but just put in null + bool success = determine_param_defaults( stmt, _FN_, *var_or_val, param_num, php_type, direction, sql_type, + sql_c_type, column_size, decimal_digits TSRMLS_CC ); + if( !success ) { + return NULL; + } + return *var_or_val; + } + + // 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 ) { + + sqlsrv_phptype sqlsrv_php_type; + int encoding; + sqlsrv_php_type = determine_sqlsrv_php_type( sql_type.typeinfo.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. + if( sqlsrv_php_type.typeinfo.type == PHPTYPE_INVALID ) DIE( "An invalid php type was returned with (supposed) validated sql type and column_sze" ); + + SQLSRV_STATIC_ASSERT( MAX_SQLSRV_PHPTYPE == ( sizeof( sqlsrv_to_php_type ) / sizeof( zend_uchar )) ); + php_type = sqlsrv_to_php_type[ sqlsrv_php_type.typeinfo.type - 1 ]; + encoding = sqlsrv_php_type.typeinfo.encoding; + sql_c_type = determine_c_type( php_type, encoding ); + } + + // if the parameter is output and the user gave us a variable that isn't of the output + // type requested, then we set the variable given to us to the type requested if the variable's + // type/value is null, otherwise we throw an error + if( direction == SQL_PARAM_OUTPUT && Z_TYPE_PP( var_or_val ) != php_type ) { + + // make sure it's not one of the invalid output param types before we handle it + switch( php_type ) { + case IS_NULL: + case IS_RESOURCE: + case IS_OBJECT: + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE TSRMLS_CC ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + break; + } + + // if the user gives us a NULL for an output parameter, we convert the variable to the appropriate type + if( Z_TYPE_PP( var_or_val ) == IS_NULL ) { + Z_TYPE_PP( var_or_val ) = php_type; + if( php_type == IS_STRING ) { + Z_STRVAL_PP( var_or_val ) = static_cast( emalloc( column_size )); + } + } + else { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_OUTPUT_PARAM_TYPE_DOESNT_MATCH TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return NULL; + } + } + + // return the variable/value + return *var_or_val; +} + +// common code for when just a variable/value is given as a parameter or when NULL is given +bool determine_param_defaults( sqlsrv_stmt const* stmt, const char* _FN_, zval const* param_z, int param_num, __out zend_uchar& php_type, __out int& direction, + __out sqlsrv_sqltype& sql_type, __out SQLSMALLINT& sql_c_type, __out SQLUINTEGER& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ) +{ + direction = SQL_PARAM_INPUT; + + php_type = Z_TYPE_P( param_z ); + sql_type = determine_sql_type( php_type, SQLSRV_ENCODING_CHAR, param_z ); + if( sql_type.typeinfo.type == SQLTYPE_INVALID ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return false; + } + sql_c_type = determine_c_type( php_type, SQLSRV_ENCODING_CHAR ); + if( sql_c_type == SQLTYPE_INVALID ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return false; + } + if( determine_column_size_or_precision( sql_type, &column_size, &decimal_digits ) == false ) { + handle_error( &stmt->ctx, LOG_STMT, _FN_, SQLSRV_ERROR_INVALID_PARAMETER_PRECISION TSRMLS_CC, param_num ); + SQLFreeStmt( stmt->ctx.handle, SQL_RESET_PARAMS ); + return false; + } + + return true; +} + +} // namespace diff --git a/stream.cpp b/stream.cpp new file mode 100644 index 00000000..6d94be22 --- /dev/null +++ b/stream.cpp @@ -0,0 +1,189 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: stream.cpp +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Contents: Implementation of PHP streams for reading SQL Server data +// +// Comments: Only certain data types may be read from streams. The data type +// are all the (var)binary and (var)(n)char types, whether sized or max types +// as well the legacy LOB types: text, ntext, and image. +// +// 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 + + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} Macros. We use LOG_STMT here since +// streams are manufactured by statement functions. +int current_log_subsystem = LOG_STMT; + + +// close a stream and free the PHP resources used by it + +int sqlsrv_stream_close( php_stream* stream, int TSRMLS_DC ) +{ + sqlsrv_stream* ss = static_cast( stream->abstract ); + + if( ss == NULL ) DIE( "sqlsrv_stream* ss is NULL. Shouldn't ever be 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 ); + zval_ptr_dtor( &ss->stmt->active_stream ); + // there is no active stream + ss->stmt->active_stream = NULL; + + efree( 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 ) +{ + SQLRETURN r = SQL_SUCCESS; + SQLINTEGER left_to_read = 0; + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSMALLINT c_type = SQL_C_CHAR; + + LOG( SEV_NOTICE, LOG_STMT, "sqlsrv_stream_read: asking for %1!d! bytes from stmt %2!d!, field %3!d!", count, ss->stmt->ctx.handle, ss->field ); + + if( stream->eof ) { + return 0; + }; + + if( ss == NULL ) DIE( "sqlsrv_stream* ss is NULL. Shouldn't ever be NULL" ); + + if( ss->encoding == SQLSRV_ENCODING_CHAR ) { + c_type = SQL_C_CHAR; + } + else { + c_type = SQL_C_BINARY; + } + + // get the data + r = SQLGetData( ss->stmt->ctx.handle, ss->field + 1, c_type, buf, count, &left_to_read ); + + // 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 || left_to_read == SQL_NULL_DATA || ( left_to_read != SQL_NO_TOTAL && static_cast( left_to_read ) <= count )) { + stream->eof = 1; + } + + // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator + // if it's a character encoded field + if( r == SQL_SUCCESS_WITH_INFO ) { + SQLRETURN r; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + r = SQLGetDiagField( SQL_HANDLE_STMT, ss->stmt->ctx.handle, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); + if( is_truncated_warning( state ) || left_to_read == SQL_NO_TOTAL ) { + if( c_type == SQL_C_CHAR ) { + --count; + } + return count; + } + } +#pragma warning(push) +#pragma warning( disable: 4714 ) + CHECK_SQL_ERROR( r, ss->stmt, "sqlsrv_stream_read", NULL, stream->eof = 1; return 0; ); +#pragma warning( pop ) + CHECK_SQL_WARNING( r, ss->stmt, "sqlsrv_stream_read", NULL ); + + return left_to_read; +} + +// 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 +}; + +#if defined(OACR) // OACR is an internal Microsoft static code analysis tool. +OACR_WARNING_PUSH +OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "STREAMS_DC is a Zend macro that evals to two char buffers." ) +#endif + +// 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. +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 ) +{ +#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 + + emalloc_auto_ptr ss; + + ss = static_cast( emalloc( 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; +} +#if defined(OACR) +OACR_WARNING_POP +#endif + +// 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. the stream +php_stream_wrapper g_sqlsrv_stream_wrapper = { + &sqlsrv_stream_wrapper_ops, + NULL, + 0 +}; diff --git a/template.rc b/template.rc index 1fac6838..50b99658 100644 --- a/template.rc +++ b/template.rc @@ -53,7 +53,7 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "Comments", "This product includes PHP softtware that is freely available from http://www.php.net/software/. © 1997-2008 The PHP Group. All rights reserved.\0 "\0" + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. © 1997-2008 The PHP Group. All rights reserved.\0" VALUE "CompanyName", "Microsoft Corp.\0" VALUE "FileDescription", FILE_DESCRIPTION "\0" VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) diff --git a/util.cpp b/util.cpp new file mode 100644 index 00000000..c2eda036 --- /dev/null +++ b/util.cpp @@ -0,0 +1,871 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: util.cpp +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Contents: Utility functions for the SQL Server 2005 Driver for PHP 1.0 +// +// Comments: Mostly error handling and some type handling +// +// 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 + + +namespace { + +// *** internal constants *** +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +int current_log_subsystem = LOG_UTIL; + +// SQLSTATE for all internal errors +const char IMSSP[] = "IMSSP"; + +// SQLSTATE for all internal warnings +const char SSPWARN[] = "01SSP"; + +// 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 +const char* internal_format_error = "An internal error occurred. FormatMessage failed writing an error message."; + +// *** internal functions *** +bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chain, zval** ignored_chain, int log_severity, int log_subsystem, + const char* _FN_, sqlsrv_error const* ssphp, va_list args TSRMLS_DC ); +bool ignore_warning( char const* sql_state, int native_code TSRMLS_DC ); + +bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC ); +int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC ); + +} + +// internal error defintions. see sqlsrv_error structure definition in php_sqlsrv.h for more information +sqlsrv_error SQLSRV_ERROR_INVALID_OPTION[] = { + { IMSSP, "Invalid option %1!s! was passed to sqlsrv_connect.", -1, true } +}; +sqlsrv_error SQLSRV_ERROR_FILE_VERSION[] = { + { IMSSP, "An error occurred when retrieving the extension version.", -2, false } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_PARAM_TYPE[] = { + { IMSSP, "An unknown type for a bound parameter was specified.", -3, false } +}; +sqlsrv_error SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED[] = { + { IMSSP, "An unescaped right brace (}) was found in option %1!s!.", -4, true } +}; +sqlsrv_error SQLSRV_ERROR_NO_DATA[] = { + { IMSSP, "Field %1!d! returned no data.", -5, true } +}; +sqlsrv_error SQLSRV_ERROR_STREAMABLE_TYPES_ONLY[] = { + { IMSSP, "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using streams.", -6, false} +}; +sqlsrv_error SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE[] = { + { IMSSP, "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", -7, false } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_CONNECTION_KEY[] = { + { IMSSP, "An invalid connection option key type was received. Option key types must be strings.", -8, false } +}; +sqlsrv_error SQLSRV_ERROR_VAR_REQUIRED[] = { + { IMSSP, "Parameter array %1!d! must have at least one value or variable.", -9, true } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_TYPE[] = { + { IMSSP, "An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY, and SQLSRV_FETCH_BOTH are acceptable values.", -10, false } +}; +sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_EXECUTED[] = { + { IMSSP, "The statement must be executed before results can be retrieved.", -11, false } +}; +sqlsrv_error SQLSRV_ERROR_ALREADY_IN_TXN[] = { + { IMSSP, "Cannot begin a transaction until the current transaction has been completed by calling either sqlsrv_commit or sqlsrv_rollback.", -12, false } +}; +sqlsrv_error SQLSRV_ERROR_NOT_IN_TXN[] = { + { IMSSP, "A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or sqlsrv_rollback.", -13, false } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER[] = { + { IMSSP, "An invalid parameter was passed to %1!s!.", -14, true } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION[] = { + { IMSSP, "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 SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE[] = { + { IMSSP, "An invalid PHP type for parameter %1!d! was specified.", -16, true } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE[] = { + { IMSSP, "An invalid SQL Server type for parameter %1!d! was specified.", -17, true } +}; +sqlsrv_error SQLSRV_ERROR_FETCH_NOT_CALLED[] = { + { IMSSP, "A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false } +}; +sqlsrv_error SQLSRV_ERROR_FIELD_INDEX_ERROR[] = { + { IMSSP, "Fields within a row must be accessed in sequential 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 SQLSRV_ERROR_DATETIME_CONVERSION_FAILED[] = { + { IMSSP, "The retrieval of the DateTime object failed.", -20, false } +}; +sqlsrv_error SQLSRV_ERROR_SERVER_INFO[] = { + { IMSSP, "An error occurred while retrieving the server information.", -21, false } +}; +sqlsrv_error SQLSRV_ERROR_FETCH_PAST_END[] = { + { IMSSP, "There are no more rows in the active result set.", -22, false } +}; +sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_PREPARED[] = { + { IMSSP, "A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false } +}; +sqlsrv_error SQLSRV_ERROR_ZEND_HASH[] = { + { IMSSP, "An error occurred while creating or accessing a Zend hash table.", -24, false } +}; +sqlsrv_error SQLSRV_ERROR_ZEND_STREAM[] = { + { IMSSP, "An error occurred while reading from a PHP stream.", -25, false } +}; +sqlsrv_error SQLSRV_ERROR_NEXT_RESULT_PAST_END[] = { + { IMSSP, "There are no more results returned by the query.", -26, false } +}; +sqlsrv_error SQLSRV_ERROR_STREAM_CREATE[] = { + { IMSSP, "An error occurred while retrieving a SQL Server field as a stream.", -27, false } +}; +sqlsrv_error SQLSRV_ERROR_NO_FIELDS[] = { + { IMSSP, "The active result for the query contains no fields.", -28, false } +}; +sqlsrv_error SQLSRV_ERROR_ZEND_BAD_CLASS[] = { + { IMSSP, "Failed to find class %1!s!.", -29, true } +}; +sqlsrv_error SQLSRV_ERROR_ZEND_OBJECT_FAILED[] = { + { IMSSP, "Failed to create an instance of class %1!s!.", -30, true } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_PRECISION[] = { + { IMSSP, "An invalid size or precision for parameter %1!d! was specified.", -31, true } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_KEY[] = { + { IMSSP, "Option %1!s! is invalid.", -32, true } +}; +sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_VALUE[] = { + { IMSSP, "Invalid value %1!s! for option %2!s! was specified.", -33, true } +}; +sqlsrv_error SQLSRV_ERROR_OUTPUT_PARAM_TYPE_DOESNT_MATCH[] = { + { IMSSP, "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 SQLSRV_ERROR_INVALID_TYPE[] = { + { IMSSP, "Invalid type", -35, false } +}; +sqlsrv_error SQLSRV_ERROR_COMMIT_FAILED[] = { + { IMSSP, "Transaction commit failed. Auto commit mode is still off.", -36, false } +}; +sqlsrv_error SQLSRV_ERROR_ROLLBACK_FAILED[] = { + { IMSSP, "Transaction rollback failed. Auto commit mode is still off.", -37, false } +}; +sqlsrv_error SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF[] = { + { IMSSP, "The transaction completed (it was either committed or rolled back). Auto commit mode is still off.", -38, false } +}; +sqlsrv_error SQLSRV_ERROR_REGISTER_RESOURCE[] = { + { IMSSP, "Registering the %1!s! resource failed.", -39, true } +}; + + +// internal warning definitions +sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[] = { + { SSPWARN, "An empty field name was skipped by sqlsrv_fetch_object.", -100, false } +}; + + + +// 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][SQL Native Client]Query timeout expired +// [message] => [Microsoft][SQL Native Client]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); + + DECL_FUNC_NAME( "sqlsrv_errors" ); + LOG_FUNCTION; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) { + 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 ) { + zval_ptr_dtor( &both_z ); + RETURN_FALSE; + } + both_z->is_ref = 1; + if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( errors ) TSRMLS_CC )) { + zend_hash_destroy( Z_ARRVAL_P( both_z )); + RETURN_FALSE; + } + + + if( Z_TYPE_P( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( warnings ) TSRMLS_CC )) { + zend_hash_destroy( Z_ARRVAL_P( both_z )); + 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 ); + + char* option; + int option_len; + zval* value_z; + + DECL_FUNC_NAME( "sqlsrv_configure" ); + LOG_FUNCTION; + + RETVAL_FALSE; + + reset_errors( TSRMLS_C ); + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ) == FAILURE ) { + handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + + if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { + if( zend_is_true( value_z )) { + SQLSRV_G( warnings_return_as_errors ) = true; + } + else { + SQLSRV_G( warnings_return_as_errors ) = false; + } + + LOG( SEV_NOTICE, LOG_UTIL, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); + + RETURN_TRUE; + } + else if( !stricmp( option, INI_LOG_SEVERITY )) { + + if( Z_TYPE_P( value_z ) != IS_LONG ) { + handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + + 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 ) = severity_mask; + + LOG( SEV_NOTICE, LOG_UTIL, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); + + RETURN_TRUE; + } + else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { + + if( Z_TYPE_P( value_z ) != IS_LONG ) { + handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + + long subsystem_mask = Z_LVAL_P( value_z ); + LOG( SEV_NOTICE, LOG_UTIL, "subsystem_mask = %1!d!", subsystem_mask ); + if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { + RETURN_FALSE; + } + + SQLSRV_G( log_subsystems ) = subsystem_mask; + + LOG( SEV_NOTICE, LOG_UTIL, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); + + RETURN_TRUE; + } + else { + + LOG( SEV_ERROR, LOG_UTIL, "Invalid option given to sqlsrv_configure" ); + + handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + + RETURN_FALSE; + } +} + + +// 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; + int option_len; + + DECL_FUNC_NAME( "sqlsrv_get_config" ); + LOG_FUNCTION; + + reset_errors( TSRMLS_C ); + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ) == FAILURE ) { + handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + RETURN_FALSE; + } + + 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 { + + LOG( SEV_ERROR, LOG_UTIL, "Invalid option given to sqlsrv_get_config." ); + + handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); + + RETURN_FALSE; + } +} + +// wrapper around handle_error that checks a condition and returns whether or not the error was ignored +bool check_sql_error_ex( bool condition, sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) +{ + if( condition ) { + + va_list args; +#if defined(ZTS) + va_start( args, TSRMLS_C ); +#else + va_start( args, ssphp); +#endif + + bool ignored = handle_error( ctx, log_subsystem, _FN_, ssphp TSRMLS_CC, args ); + if( ignored ) { + // this will print immediately after the error information in the log + LOG( SEV_ERROR, log_subsystem, "error ignored" ); + } + + va_end( args ); + + return ignored; + } + + return true; +} + +// this is a special function for sqlsrv internal warnings. It emits an internal warning and treats +// it as an error if the WarningsReturnAsErrors flag is set. +bool check_sqlsrv_warnings( bool condition, sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) +{ + // we have to have warning as there is no ODBC error or warning + assert( ssphp != NULL ); + + if( condition ) { + + va_list args; +#if defined(ZTS) + va_start( args, TSRMLS_C ); +#else + va_start( args, ssphp); +#endif + if( SQLSRV_G( warnings_return_as_errors )) { + bool ignored = handle_error( ctx, log_subsystem, _FN_, ssphp TSRMLS_CC, args ); + if( ignored ) { + // this will print immediately after the error information in the log + LOG( SEV_ERROR, log_subsystem, "error ignored" ); + } + + va_end( args ); + + return ignored; + } + else { + handle_warning( ctx, log_subsystem, _FN_, ssphp TSRMLS_CC, args ); + } + + va_end( args ); + } + + return true; +} + +// wrapper for errors around the common handle_errors_and_warnings +bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) +{ + va_list args; +#if defined(ZTS) + va_start( args, TSRMLS_C ); +#else + va_start( args, ssphp ); +#endif + LOG( SEV_NOTICE, LOG_UTIL, "handle_error: entered for function %1!s!", _FN_ ); + // put errors (including warnings treated as errors) into errors and ignored + // warnings into warnings + bool ignored = handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), SEV_ERROR, log_subsystem, _FN_, + ssphp, args TSRMLS_CC ); + + va_end( args ); + + return ignored; +} + +// wrapper for warnings around the common handle_errors_and_warnings +void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) +{ + va_list args; +#if defined(ZTS) + va_start( args, TSRMLS_C ); +#else + va_start( args, ssphp ); +#endif + LOG( SEV_NOTICE, LOG_UTIL, "handle_warning: entered for function %1!s!", _FN_ ); + // put all warnings into the warnings hash table and don't ignore any. No warnings here are treated as errors. + handle_errors_and_warnings( ctx, &SQLSRV_G( warnings ), NULL, SEV_WARNING, log_subsystem, _FN_, + ssphp, args TSRMLS_CC ); + + va_end( args ); +} + +// 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, unsigned int subsystem TSRMLS_DC, const char* msg, ...) +{ + va_list args; + va_start( args, msg ); + + if( (severity & SQLSRV_G( log_severity )) && (subsystem & SQLSRV_G( log_subsystems ))) { + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, &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 ); + } + + va_end( args ); +} + +// *** internal function implementations *** + +namespace { + +// there are actually two error arrays (potentially) constructed by this function. +// The reported chain is that set of diagnostics that is not in the list of warnings to not report as errors. In other words, it's +// the list of normally processed diagnostics. +// The ignored chain is that set of diagnostics which were specifically ignored and not reported. It is possible for the caller +// to specify no ignored chain by setting the parameter to NULL. + +bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chain, zval** ignored_chain, int log_severity, int log_subsystem, + const char* _FN_, sqlsrv_error const* ssphp, va_list args TSRMLS_DC ) +{ + zval* ssphp_z = NULL; + SQLSMALLINT record_number = 1; + SQLCHAR sql_state[6]; + SQLCHAR message_text[ SQL_MAX_MESSAGE_LENGTH + 1 ]; + SQLINTEGER native_error = 0; + SQLSMALLINT message_len = 0; + SQLRETURN r = SQL_SUCCESS; + zval* temp = NULL; + bool reported_chain_was_null = false; + bool ignored_chain_was_null = false; + int zr = SUCCESS; + int reported_before = 0; + int ignored_before = 0; + + LOG( SEV_NOTICE, LOG_UTIL, "handle_errors_and_warnings: entering" ); + + // create an array of arrays + if( Z_TYPE_P( *reported_chain ) == IS_NULL ) { + reported_chain_was_null = true; + reported_before = 0; + zr = array_init( *reported_chain ); + if( zr == FAILURE ) { + DIE( "Fatal error in handle_errors_and_warnings" ); + } + } + else { + reported_before = zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )); + } + + if( ignored_chain != NULL ) { + + if( Z_TYPE_P( *ignored_chain ) == IS_NULL ) { + ignored_chain_was_null = true; + ignored_before = 0; + zr = array_init( *ignored_chain ); + if( zr == FAILURE ) { + DIE( "Fatal error in handle_errors_and_warnings" ); + } + } + else { + ignored_before = zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )); + } + } + else { + ignored_before = 0; + } + + // add the PHP error first if there is one. + // We use a while loop to allow the break to exit the loop and avoid having to use a goto for error handling + // the break at the end of the loop assures that we don't get stuck here. + while( ssphp ) { + + emalloc_auto_ptr ssphp_new; + emalloc_auto_ptr ssphp_new_message; + + if( ssphp->format ) { + ssphp_new = static_cast( emalloc( sizeof( sqlsrv_error ))); + ssphp_new->native_message = ssphp_new_message = static_cast( emalloc( SQL_MAX_MESSAGE_LENGTH )); + ssphp_new->sqlstate = ssphp->sqlstate; + ssphp_new->native_code = ssphp->native_code; + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, const_cast( ssphp->native_message ), 0, 0, + const_cast( ssphp_new->native_message ), SQL_MAX_MESSAGE_LENGTH, &args ); + if( rc == 0 ) { + ssphp_new->native_message = internal_format_error; + } + ssphp = ssphp_new; + } + + // log the error first in case of failures below + LOG( log_severity, log_subsystem, "%1!s!: SQLSTATE = %2!s!", _FN_, ssphp->sqlstate ); + LOG( log_severity, log_subsystem, "%1!s!: error code = %2!d!", _FN_, ssphp->native_code ); + LOG( log_severity, log_subsystem, "%1!s!: message = %2!s!", _FN_, ssphp->native_message ); + + MAKE_STD_ZVAL( ssphp_z ); + zr = array_init( ssphp_z ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + // add the error info to the array + MAKE_STD_ZVAL( temp ); + ZVAL_STRINGL( temp, const_cast( ssphp->sqlstate ), SQL_SQLSTATE_SIZE, 1 ); + zr = add_next_index_zval( ssphp_z, temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + zr = add_assoc_zval( ssphp_z, "SQLSTATE", temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + MAKE_STD_ZVAL( temp ); + ZVAL_LONG( temp, ssphp->native_code ); + zr = add_next_index_zval( ssphp_z, temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + zr = add_assoc_zval( ssphp_z, "code", temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + MAKE_STD_ZVAL( temp ); + ZVAL_STRING( temp, const_cast( ssphp->native_message ), 1 ); + zr = add_next_index_zval( ssphp_z, temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + zr = add_assoc_zval( ssphp_z, "message", temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + + if( ignore_warning( ssphp->sqlstate, ssphp->native_code TSRMLS_CC ) && ignored_chain != NULL ) { + zr = add_next_index_zval( *ignored_chain, ssphp_z ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + } + else { + zr = add_next_index_zval( *reported_chain, ssphp_z ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + break; + } + } + + break; // exit the "loop" always + } + + if( ctx ) { + + SQLHANDLE h = ctx->handle; + SQLSMALLINT h_type = ctx->handle_type; + for( r = SQLGetDiagRec( h_type, h, record_number, sql_state, &native_error, message_text, SQL_MAX_MESSAGE_LENGTH + 1, &message_len ); + SQL_SUCCEEDED( r ); + ++record_number, + r = SQLGetDiagRec( h_type, h, record_number, sql_state, &native_error, message_text, SQL_MAX_MESSAGE_LENGTH + 1, &message_len )) { + + + // log the result first + LOG( log_severity, log_subsystem, "%1!s!: SQLSTATE = %2!s!", _FN_, sql_state ); + LOG( log_severity, log_subsystem, "%1!s!: error code = %2!d!", _FN_, native_error ); + LOG( log_severity, log_subsystem, "%1!s!: message = %2!s!", _FN_, message_text ); + + MAKE_STD_ZVAL( ssphp_z ); + zr = array_init( ssphp_z ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + // add the error info to the array + MAKE_STD_ZVAL( temp ); + ZVAL_STRINGL( temp, reinterpret_cast( sql_state ), SQL_SQLSTATE_SIZE, 1 ); + zr = add_next_index_zval( ssphp_z, temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + zr = add_assoc_zval( ssphp_z, "SQLSTATE", temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + MAKE_STD_ZVAL( temp ); + ZVAL_LONG( temp, native_error ); + zr = add_next_index_zval( ssphp_z, temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + zr = add_assoc_zval( ssphp_z, "code", temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + MAKE_STD_ZVAL( temp ); + ZVAL_STRINGL( temp, reinterpret_cast( message_text ), message_len, 1 ); + zr = add_next_index_zval( ssphp_z, temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + zr = add_assoc_zval( ssphp_z, "message", temp ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + + if( ignore_warning( reinterpret_cast( sql_state ), native_error TSRMLS_CC ) && ignored_chain != NULL ) { + zr = add_next_index_zval( *ignored_chain, ssphp_z ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + } + else { + zr = add_next_index_zval( *reported_chain, ssphp_z ); + if( zr == FAILURE ) { + zval_ptr_dtor( &ssphp_z ); + continue; + } + } + } + } + + bool all_errors_ignored = ( zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) == reported_before ) && + ( ignored_chain != NULL && zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )) > ignored_before ); + + // 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 ); + } + + return all_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 const* sql_state, int native_code TSRMLS_DC ) +{ + for( zend_hash_internal_pointer_reset( SQLSRV_G( warnings_to_ignore )); + zend_hash_has_more_elements( SQLSRV_G( warnings_to_ignore ) ) == SUCCESS; + zend_hash_move_forward( SQLSRV_G( warnings_to_ignore ) ) ) { + + void* error_v; + sqlsrv_error* error; + int result; + + result = zend_hash_get_current_data( SQLSRV_G( warnings_to_ignore ), (void**) &error_v ); + if( result == FAILURE ) { + return false; + } + + error = static_cast( error_v ); + if( !strncmp( error->sqlstate, sql_state, SQL_SQLSTATE_SIZE ) && ( error->native_code == native_code || error->native_code == -1 )) { + return true; + } + } + + return false; +} + +// used by sqlsrv_merge_zend_hash below +int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC ) +{ +#if defined(ZTS) + SQLSRV_UNUSED( tsrm_ls ); +#endif + zval_ptr_dtor( reinterpret_cast( &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( 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 diff --git a/version.h b/version.h new file mode 100644 index 00000000..fde628c5 --- /dev/null +++ b/version.h @@ -0,0 +1,19 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: version.h +// +// 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. +//---------------------------------------------------------------------------------------------------------------------------------- + +#define VER_FILEVERSION_STR "1.0.0.0" +#define _FILEVERSION 1,0,0,0 +#define SQLVERSION_MAJOR 1 +#define SQLVERSION_MINOR 0 +#define SQLVERSION_MMDD 0 +#define SQLVERSION_REVISION 0