Initial commit of the SQL Server 2005 Driver for PHP 1.0

Please see the enclosed README.TXT for more information.

This software is licensed under the Microsoft Public License.  The license is available for viewing at http://codeplex.com/SQL2K5PHP/license
This commit is contained in:
icosahedron 2008-07-28 21:49:47 +00:00
parent 2d75dd4953
commit ed20a39316
10 changed files with 6687 additions and 1 deletions

34
README.TXT Normal file
View file

@ -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 <other options>" to
generate the makefile. run "cscript configure.js --help" to see what other
options are available. It is possible (and even probable) that other
extensions will have to be disabled or enabled for the compile to succeed.
Search 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.

40
config.w32 Normal file
View file

@ -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_' );
}
}

1262
conn.cpp Normal file
View file

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

641
init.cpp Normal file
View file

@ -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<SQLPOINTER>( 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<SQLPOINTER>( 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<SQLPOINTER>( 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<SQLPOINTER>( 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<sqlsrv_henv*>( 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;
}

1082
php_sqlsrv.h Normal file
View file

@ -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.h>
OACR_WARNING_PUSH
OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." )
OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." )
OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." )
OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." )
#endif
extern "C" {
#pragma warning(push)
#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 )
#ifdef ZTS
#include "TSRM.h"
#endif
#if _MSC_VER >= 1400
// typedef and macro to prevent a conflict between php.h and ws2tcpip.h.
// php.h defines this constant as unsigned int which causes a compile error
// in ws2tcpip.h. Fortunately php.h allows an override by defining
// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define
// socklen_t here and override the php.h version.
typedef int socklen_t;
#define HAVE_SOCKLEN_T
#endif
#include "php.h"
#include "php_globals.h"
#include "php_ini.h"
#include "ext/standard/php_standard.h"
#include "ext/standard/info.h"
#pragma warning(pop)
#if ZEND_DEBUG
// debug build causes warning C4505 to pop up from the Zend header files
#pragma warning( disable: 4505 )
#endif
} // extern "C"
#if defined(OACR)
OACR_WARNING_POP
#endif
#include <sql.h>
#include <sqlext.h>
// 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 <algorithm>
#include <cassert>
#include <strsafe.h>
// 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 <bool b>
struct sqlsrv_static_assert;
template <>
struct sqlsrv_static_assert<true> { static const int value = 1; };
#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() )
//**********************************************************************************************************************************
// 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<char*>( state )) != 5 ) { DIE( "Incorrect SQLSTATE given to is_truncated_warning" ); }
#endif
return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4');
}
// 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 <typename H>
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<char*>( 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<H*>( 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 <typename H>
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<char*>( 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<H*>( 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 <typename H>
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<char*>( 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<H*>( 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 <typename H>
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<char*>( 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<H*>( 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 <typename H>
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<char*>( 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<H*>( 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 <typename H>
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<char*>( 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<H*>( 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 <typename T>
struct remove_const {
typedef T type;
};
template <typename T>
struct remove_const<const T*> {
typedef T* type;
};
// base class for auto_ptrs that we define below. It provides common operators and functions
// used by all the classes.
template <typename T, typename Subclass>
class sqlsrv_auto_ptr {
public:
sqlsrv_auto_ptr( void ) : _ptr( NULL )
{
}
~sqlsrv_auto_ptr( void )
{
static_cast<Subclass*>(this)->reset( NULL );
}
// call when ownership is transferred
void transferred( void )
{
_ptr = NULL;
}
// explicit function to get the pointer.
T* get( void ) const
{
return _ptr;
}
// cast operator to allow auto_ptr to be used where a normal const * can be.
operator const T* () const
{
return _ptr;
}
// cast operator to allow auto_ptr to be used where a normal pointer can be.
operator typename remove_const<T*>::type () const
{
return _ptr;
}
// 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<Subclass*>(this)->reset( src._ptr );
}
src.transferred();
}
sqlsrv_auto_ptr( typename Subclass const& src )
{
if( _ptr ) {
static_cast<Subclass*>( this )->reset( src._ptr );
}
src.transferred();
}
// assign a new pointer to the auto_ptr. It will free the previous memory block
// because ownership is deemed finished.
T* operator=( T* ptr )
{
static_cast<Subclass*>( this )->reset( ptr );
return ptr;
}
T* _ptr;
};
// an auto_ptr for 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 <typename T>
class emalloc_auto_ptr : public sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> > {
public:
emalloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> >( NULL )
{
}
emalloc_auto_ptr( const emalloc_auto_ptr& src )
{
sqlsrv_auto_ptr<T, emalloc_auto_ptr<T> >::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<T, emalloc_auto_ptr<T> >::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<HashTable, hash_auto_ptr> {
public:
hash_auto_ptr( void ) :
sqlsrv_auto_ptr<HashTable, hash_auto_ptr>( NULL )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( HashTable* ptr = NULL )
{
if( _ptr ) {
zend_hash_destroy( _ptr );
FREE_HASHTABLE( _ptr );
}
_ptr = ptr;
}
HashTable* operator=( HashTable* ptr )
{
return sqlsrv_auto_ptr<HashTable, hash_auto_ptr>::operator=( ptr );
}
private:
hash_auto_ptr( HashTable const& hash );
hash_auto_ptr( hash_auto_ptr const& hash );
};
// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr.
// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed
// (out of scope) or ownership is transferred using the function "transferred".
class zval_auto_ptr : public sqlsrv_auto_ptr<zval, zval_auto_ptr> {
public:
zval_auto_ptr( void )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( zval* ptr = NULL )
{
if( _ptr )
zval_ptr_dtor( &_ptr );
_ptr = ptr;
}
zval* operator=( zval* ptr )
{
return sqlsrv_auto_ptr<zval, zval_auto_ptr>::operator=( ptr );
}
private:
zval_auto_ptr( const zval_auto_ptr& src );
};
#endif /* PHP_SQLSRV_H */

2548
stmt.cpp Normal file
View file

@ -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 <sal.h>
// *** 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<sqlsrv_stmt>( INTERNAL_FUNCTION_PARAM_PASSTHRU, LOG_STMT, const_cast<char*>( 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<sqlsrv_stmt*>( 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<char> 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<zval**> params_m;
zval_auto_ptr ctor_retval_z;
ALLOC_INIT_ZVAL( ctor_retval_z );
int num_params = 0;
if( ctor_params_z != NULL ) {
HashTable* ctorp_ht = Z_ARRVAL_P( ctor_params_z );
num_params = zend_hash_num_elements( ctorp_ht );
params_m = reinterpret_cast<zval***>( 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<void**>(&(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<char> 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<char*>( 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<SQLCHAR*>( 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<SQLUSMALLINT>( 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<SQLUSMALLINT>( 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<SQLUSMALLINT>( 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<SQLINTEGER> 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<SQLINTEGER*>( 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**) &param_zz );
CHECK_SQL_ERROR_EX( success == FAILURE, stmt, _FN_, SQLSRV_ERROR_VAR_REQUIRED, return false; );
param_z = *static_cast<zval**>( 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 = &param_z->value;
buffer_len = sizeof( param_z->value.lval );
ind_ptr[ i-1 ] = buffer_len;
break;
case IS_DOUBLE:
buffer = &param_z->value;
buffer_len = sizeof( param_z->value.dval );
ind_ptr[ i-1 ] = buffer_len;
break;
case IS_STRING:
buffer = Z_STRVAL_PP( &param_z );
buffer_len = Z_STRLEN_PP( &param_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<char*>( 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<char*>( 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, &param_z );
buffer = param_z;
zval_add_ref( &param_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<SQLSMALLINT>( 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<SQLCHAR*>( 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<sqlsrv_stmt*>( 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<zval**>( 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<sqlsrv_stmt*>( 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<sqlsrv_stmt*>( 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( &param_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<SQLPOINTER*>( &param_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<sqlsrv_stream*>( 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<SQLUSMALLINT>( 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<char*>( 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<char*>( 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<char*>( 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<char*>( 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<char*>( alloca( field_name_len_max ));
SQLSMALLINT field_name_len;
emalloc_auto_ptr<sqlsrv_fetch_field> field_names;
field_names = static_cast<sqlsrv_fetch_field*>( 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<char*>( 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<char*>( 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<char*>( 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

189
stream.cpp Normal file
View file

@ -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 <windows.h>
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<sqlsrv_stream*>( 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<sqlsrv_stream*>( 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<size_t>( 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<sqlsrv_stream> ss;
ss = static_cast<sqlsrv_stream*>( 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
};

View file

@ -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)

871
util.cpp Normal file
View file

@ -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 <windows.h>
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<sqlsrv_error> ssphp_new;
emalloc_auto_ptr<const char> ssphp_new_message;
if( ssphp->format ) {
ssphp_new = static_cast<sqlsrv_error*>( emalloc( sizeof( sqlsrv_error )));
ssphp_new->native_message = ssphp_new_message = static_cast<char const*>( 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<LPSTR>( ssphp->native_message ), 0, 0,
const_cast<LPSTR>( 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<char*>( 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<char*>( 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<char*>( 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<char*>( 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<const char*>( 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<sqlsrv_error*>( 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<zval**>( &dest ));
return ZEND_HASH_APPLY_REMOVE;
}
// sqlsrv_merge_zend_hash
// merge a source hash into a dest hash table and return any errors.
bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC )
{
#if defined(ZTS)
SQLSRV_UNUSED( tsrm_ls );
#endif
if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" );
if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" );
if( Z_TYPE_P( src_z ) == IS_NULL ) {
return true;
}
HashTable* src_ht = Z_ARRVAL_P( src_z );
int result = SUCCESS;
for( zend_hash_internal_pointer_reset( src_ht );
zend_hash_has_more_elements( src_ht ) == SUCCESS;
zend_hash_move_forward( src_ht ) ) {
void * value_v;
zval* value_z;
result = zend_hash_get_current_data( src_ht, (void**) &value_v );
if( result == FAILURE ) {
zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC );
return false;
}
value_z = *(static_cast<zval**>( value_v ));
result = add_next_index_zval( dest_z, value_z );
if( result == FAILURE ) {
zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC );
return false;
}
zval_add_ref( &value_z );
}
return true;
}
} // namespace

19
version.h Normal file
View file

@ -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