HEX
Server: Apache/2.4.57 (Unix) OpenSSL/1.0.2k-fips
System: Linux panel19.servidoresrapidos.net 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
User: pastorfi (1081)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: //usr/local/src/sqlsrv-5.10.0beta1/pdo_sqlsrv-5.10.0beta1/pdo_stmt.cpp
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_stmt.cpp
//
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), 
//  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
//  IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------

extern "C" {
  #include "php_pdo_sqlsrv.h"
}

#include "php_pdo_sqlsrv_int.h"

// *** internal variables and constants ***

namespace {

// Maps to the list of PDO::FETCH_ORI_* constants
SQLSMALLINT odbc_fetch_orientation[] =
{
    SQL_FETCH_NEXT,         // PDO_FETCH_ORI_NEXT
    SQL_FETCH_PRIOR,        // PDO_FETCH_ORI_PRIOR
    SQL_FETCH_FIRST,        // PDO_FETCH_ORI_FIRST
    SQL_FETCH_LAST,         // PDO_FETCH_ORI_LAST
    SQL_FETCH_ABSOLUTE,     // PDO_FETCH_ORI_ABS
    SQL_FETCH_RELATIVE      // PDO_FETCH_ORI_REL
};

// max length of a field type
const int SQL_SERVER_IDENT_SIZE_MAX = 128;

inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori ( _In_ enum pdo_fetch_orientation ori )
{
    SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range.");
#ifdef _WIN32
    OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
    OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" );
#endif
    return odbc_fetch_orientation[ori];
}

// Returns SQLSRV data type for a given PDO type. See pdo_param_type
// for list of supported pdo types.
SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type )
{
    pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>(driver_stmt);
    SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null");
    
    switch( pdo_type ) {

        case PDO_PARAM_BOOL:
        case PDO_PARAM_INT:
            return SQLSRV_PHPTYPE_INT;

        case PDO_PARAM_STR:
            return SQLSRV_PHPTYPE_STRING;
        
        case PDO_PARAM_NULL:
            return SQLSRV_PHPTYPE_NULL;
        
        case PDO_PARAM_LOB:
            if (pdo_stmt->fetch_datetime) {
                return SQLSRV_PHPTYPE_DATETIME;
            } else {
                // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented.
                return SQLSRV_PHPTYPE_STRING;
            }
        case PDO_PARAM_STMT:
            THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
            break;

        default:
            DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" );
    }
    
    return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning 
}

// Returns a pdo type for a given SQL type. See pdo_param_type
// for list of supported pdo types.
inline pdo_param_type sql_type_to_pdo_type( _In_ SQLSMALLINT sql_type )
{
    pdo_param_type return_type = PDO_PARAM_STR;

    switch( sql_type ) {

        case SQL_BIT:
        case SQL_INTEGER:
        case SQL_SMALLINT:
        case SQL_TINYINT:
        case SQL_BIGINT:
        case SQL_BINARY:
        case SQL_CHAR:
        case SQL_DECIMAL:
        case SQL_DOUBLE: 
        case SQL_FLOAT:
        case SQL_GUID:
        case SQL_LONGVARBINARY:
        case SQL_LONGVARCHAR:
        case SQL_NUMERIC:
        case SQL_REAL:
        case SQL_SS_TIME2:
        case SQL_SS_TIMESTAMPOFFSET:
        case SQL_SS_UDT:
        case SQL_SS_VARIANT:
        case SQL_SS_XML:
        case SQL_TYPE_DATE:
        case SQL_TYPE_TIMESTAMP:
        case SQL_VARBINARY:
        case SQL_VARCHAR:
        case SQL_WCHAR:
        case SQL_WLONGVARCHAR:
        case SQL_WVARCHAR:
            return_type = PDO_PARAM_STR;
            break;

        default: {
            DIE( "sql_type_to_pdo_type: Invalid SQL type provided." );
            break;
        }
    }

    return return_type;
}

// Calls core_sqlsrv_set_scrollable function to set cursor.
// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL.
void set_stmt_cursors( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
    if( Z_TYPE_P( value_z ) != IS_LONG ) {

        THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
    }

    zend_long pdo_cursor_type = Z_LVAL_P( value_z );
    long odbc_cursor_type = -1;

    switch( pdo_cursor_type ) {

        case PDO_CURSOR_FWDONLY:
            odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY;
            break;

        case PDO_CURSOR_SCROLL:
            odbc_cursor_type = SQL_CURSOR_STATIC;
            break;

        default:
            THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
    }   

    core_sqlsrv_set_scrollable( stmt, odbc_cursor_type ); 
}

void set_stmt_cursor_scroll_type( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
    if( Z_TYPE_P( value_z ) != IS_LONG ) {

        THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
    }

    if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) {

        THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE );
    }

    long odbc_cursor_type = static_cast<long>( Z_LVAL_P( value_z ) );

    core_sqlsrv_set_scrollable( stmt, odbc_cursor_type ); 

    return;
}

// Sets the statement encoding. Default encoding on the statement 
// implies use the connection's encoding.
void set_stmt_encoding( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
    // validate the value
    if( Z_TYPE_P( value_z ) != IS_LONG ) {

        THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
    }
        
    zend_long attr_value = Z_LVAL_P( value_z );
    
    switch( attr_value ) {

        // when the default encoding is applied to a statement, it means use the creating connection's encoding
        case SQLSRV_ENCODING_DEFAULT:
        case SQLSRV_ENCODING_BINARY:
        case SQLSRV_ENCODING_SYSTEM:
        case SQLSRV_ENCODING_UTF8:
            stmt->set_encoding( static_cast<SQLSRV_ENCODING>( attr_value ));
            break;

        default:
            THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
            break;
    }
}

zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len )
{
    zval out_zval;
    ZVAL_UNDEF(&out_zval);

    switch (sqlsrv_php_type) {

    case SQLSRV_PHPTYPE_INT:
    case SQLSRV_PHPTYPE_FLOAT:
    {
        if (*in_val == NULL) {
            ZVAL_NULL(&out_zval);
        }
        else {

            if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) {
                ZVAL_LONG(&out_zval, **(reinterpret_cast<int**>(in_val)));
            }
            else {
                ZVAL_DOUBLE(&out_zval, **(reinterpret_cast<double**>(in_val)));
            }
        }

        if (*in_val) {
            sqlsrv_free(*in_val);
        }

        break;
    }
    case SQLSRV_PHPTYPE_STRING:
    case SQLSRV_PHPTYPE_STREAM:     // TODO: this will be moved when output streaming is implemented
    {
        if (*in_val == NULL) {

            ZVAL_NULL(&out_zval);
        }
        else {

            ZVAL_STRINGL(&out_zval, reinterpret_cast<char*>(*in_val), field_len);
            sqlsrv_free(*in_val);
        }
        break;
    }
    case SQLSRV_PHPTYPE_DATETIME:
        convert_datetime_string_to_zval(stmt, static_cast<char*>(*in_val), field_len, out_zval);
        sqlsrv_free(*in_val);
        break;
    case SQLSRV_PHPTYPE_NULL:
        ZVAL_NULL(&out_zval);
        break;
    default:
        DIE("Unknown php type");
        break;
    }

    return out_zval;
}

}       // namespace 

int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori,
                          _In_ zend_long offset );
int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
                               _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type );
int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno );
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
                                 _Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees );
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result, _Inout_ enum pdo_param_type *type);
#endif

int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val );
int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt );

struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = {

    pdo_sqlsrv_stmt_dtor,
    pdo_sqlsrv_stmt_execute,
    pdo_sqlsrv_stmt_fetch,
    pdo_sqlsrv_stmt_describe_col,
    pdo_sqlsrv_stmt_get_col_data,
    pdo_sqlsrv_stmt_param_hook,
    pdo_sqlsrv_stmt_set_attr,
    pdo_sqlsrv_stmt_get_attr,
    pdo_sqlsrv_stmt_get_col_meta,
    pdo_sqlsrv_stmt_next_rowset,
    pdo_sqlsrv_stmt_close_cursor

};

void stmt_option_pdo_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    set_stmt_cursors( stmt, value_z ); 
}

void stmt_option_encoding:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    set_stmt_encoding( stmt, value_z );
}

void stmt_option_direct_query:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
    pdo_stmt->direct_query = zend_is_true(value_z);
}

void stmt_option_cursor_scroll_type:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    set_stmt_cursor_scroll_type( stmt, value_z );
}

void stmt_option_emulate_prepares:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    pdo_stmt_t *pdo_stmt = static_cast<pdo_stmt_t*>( stmt->driver() );
    pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL;
}

void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
    pdo_stmt->fetch_numeric = zend_is_true(value_z);
}

void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
    pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
    pdo_stmt->fetch_datetime = zend_is_true(value_z);
}

// log a function entry point
#define PDO_LOG_STMT_ENTRY \
{ \
    pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data ); \
    if (driver_stmt != NULL) driver_stmt->set_func( __FUNCTION__ ); \
    core_sqlsrv_register_severity_checker(pdo_severity_check); \
    LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}

// PDO SQLSRV statement destructor
pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void )
{
    if( bound_column_param_types ) {
        sqlsrv_free( bound_column_param_types );
        bound_column_param_types = NULL;
    }

    if( direct_query_subst_string ) {
        // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme
        // and we use estrdup to allocate this string, which uses emalloc
        efree( reinterpret_cast<void*>( const_cast<char*>( direct_query_subst_string )));
    }
}


// pdo_sqlsrv_stmt_close_cursor
// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor.
// Parameters:
// *stmt - Pointer to current statement
// Return:
// Returns 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt )
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    try {

        SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: pdo_stmt object was null" );

        sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );

        SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: driver_data object was null" );

        // to "close the cursor" means we make the statement ready for execution again.  To do this, we 
        // skip all the result sets on the current statement.
        // If the statement has not been executed there are no next results to iterate over.
        if ( driver_stmt && driver_stmt->executed == true )
        {
            while( driver_stmt && driver_stmt->past_next_result_end == false ) {
                core_sqlsrv_next_result( driver_stmt );
            }
        }
    }
    catch( core::CoreException& ) {

        return 0;
    }
    catch( ... ) {

        DIE( "pdo_sqlsrv_stmt_close_cursor: Unknown exception occurred while advancing to the next result set." );
    }

    return 1;
}

// pdo_sqlsrv_stmt_describe_col
// Gets the metadata for a column based on the column number. 
// Calls the core_sqlsrv_field_metadata function present in the core layer.
// Parameters:
// *stmt - pointer to current statement
// colno - Index of the column which requires description.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno)
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." );
    SQLSRV_ASSERT( stmt->driver_data != NULL, "pdo_sqlsrv_stmt_describe_col: driver_data object was NULL." );

    sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;

    try {
        
        core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data ), colno );
    }

    catch( core::CoreException& ) {
        return 0;
    }

    catch(...) {
        DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." );
    }

    pdo_column_data* column_data = &(stmt->columns[colno]);

    SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" );
   
    // Set the name
    column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 );

    // Set the maxlen
    column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size;
        
    // Set the precision
    column_data->precision = core_meta_data->field_scale;

#if PHP_VERSION_ID < 80100
    // Set the param_type    
    column_data->param_type = PDO_PARAM_ZVAL; 
#endif    
    // store the field data for use by pdo_sqlsrv_stmt_get_col_data
    pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
    SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" );
    driver_stmt->current_meta_data.push_back( core_meta_data.get() );
    SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" );
    core_meta_data.transferred();

    return 1;
}


// pdo_sqlsrv_stmt_dtor
// Maps to PDOStatement::__destruct. Destructor for the PDO Statement. 
// Parameters:
// *stmt - pointer to current statement
// Return:
// 1 for success.
int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt )
{
    pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );

    LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" );

    // if a PDO statement didn't complete preparation, its driver_data can be NULL
    if (driver_stmt == NULL) {
        return 1;
    }

    // occasionally stmt->dbh->driver_data is already freed and reset but its driver_data is not
    if (stmt->dbh != NULL && stmt->dbh->driver_data == NULL) {
        stmt->driver_data = NULL;
        return 1;
    }

    if ( driver_stmt->placeholders != NULL ) {
        zend_hash_destroy( driver_stmt->placeholders );
        FREE_HASHTABLE( driver_stmt->placeholders );
        driver_stmt->placeholders = NULL;
    }

    (( sqlsrv_stmt* )driver_stmt )->~sqlsrv_stmt();

    sqlsrv_free( driver_stmt );

    stmt->driver_data = NULL;

    return 1;
}

// pdo_sqlsrv_stmt_execute
// Maps to PDOStatement::Execute. Executes the prepared statement. 
// Parameters:
// *stmt - pointer to the current statement.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt )
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    try {

        pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
        SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" );

        // prepare for execution by flushing anything remaining in the result set if it wasn't already
        // done before binding parameters
        if( driver_stmt && driver_stmt->executed && !driver_stmt->past_next_result_end ) {

            while( driver_stmt->past_next_result_end == false ) {

                core_sqlsrv_next_result( driver_stmt, false );
            }
        }
        
        const char* query = NULL;
        unsigned int query_len = 0;

        // if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here
        if( driver_stmt->direct_query ) {

            query = driver_stmt->direct_query_subst_string;
            query_len = static_cast<unsigned int>( driver_stmt->direct_query_subst_string_len );
        }

        // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the 
        // subtituted query provided by PDO
        if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
            // reset the placeholders hashtable internal in case the user reexecutes a statement
            // Normally it's not a good idea to alter the internal pointer in a hashed array 
            // (see pull request 634 on GitHub) but in this case this is for internal use only

            zend_hash_internal_pointer_reset(driver_stmt->placeholders);

#if PHP_VERSION_ID < 80100
            query = stmt->active_query_string;
            query_len = static_cast<unsigned int>(stmt->active_query_stringlen);
#else
            query = ZSTR_VAL(stmt->active_query_string);
            query_len = ZSTR_LEN(stmt->active_query_string);
#endif
        }

        // The query timeout setting is inherited from the corresponding connection attribute, but
        // the user may have changed the query timeout setting again before this via 
        // PDOStatement::setAttribute()
        driver_stmt->set_query_timeout();
 
        SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, query, query_len );

        if ( execReturn == SQL_NO_DATA ) {
            stmt->column_count = 0;
            stmt->row_count = 0;
            driver_stmt->column_count = 0;
            driver_stmt->row_count = 0;
        }
        else {
            if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
                stmt->column_count = core::SQLNumResultCols( driver_stmt );
                driver_stmt->column_count = stmt->column_count;
            }
            else {
                stmt->column_count = driver_stmt->column_count;
            }

            if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) {
                // return the row count regardless if there are any rows or not
                stmt->row_count = core::SQLRowCount( driver_stmt );
                driver_stmt->row_count = stmt->row_count;
            }
            else {
                stmt->row_count = driver_stmt->row_count;
            }
        }

        // workaround for a bug in the PDO driver manager.  It is fairly simple to crash the PDO driver manager with 
        // the following sequence:
        // 1) Prepare and execute a statement (that has some results with it)
        // 2) call PDOStatement::nextRowset until there are no more results
        // 3) execute the statement again
        // 4) call PDOStatement::getColumnMeta
        // It crashes from what I can tell because there is no metadata because there was no call to 
        // pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to
        // PDO::execute.  My guess is that because stmt->executed is true, it optimizes away a necessary call to
        // pdo_sqlsrv_stmt_describe_col.  By setting the stmt->executed flag to 0, this call is not optimized away
        // and the crash disappears.
        if( stmt->columns == NULL ) {
            stmt->executed = 0;
        }
    }
    catch( core::CoreException& /*e*/ ) {

        return 0;

    }
    catch( ... ) {

        DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." );

    }

    // success
    return 1;
}



// pdo_sqlsrv_stmt_fetch
// Maps to PDOStatement::fetch
// Move the cursor to the record indicated. If the cursor is moved off the end, 
// or before the beginning if a scrollable cursor is created, then FAILURE is returned.  
// Parameters:
// *stmt - pointer to current statement for which the cursor should be moved.
// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants
// offset - For orientations that use it, offset to move to.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori,
                          _In_ zend_long offset)
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    try {
        
        SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" );

        pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );

        SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" );

        // set the types for bound columns to zval so that PDO does no conversion when the value
        // is returned by pdo_sqlsrv_get_col_data.  Remember the types that were bound by the user
        // and use it to manually convert data types
        if( stmt->bound_columns ) {

            pdo_bound_param_data* bind_data = NULL;

            if( !driver_stmt->bound_column_param_types ) {
#if PHP_VERSION_ID < 80100
                driver_stmt->bound_column_param_types =
                    reinterpret_cast<pdo_param_type*>( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 ));
                std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
                           PDO_PARAM_ZVAL );
#else
                // TODO: possibly no longer need bound_column_param_types?? default to PDO_PARAM_STR???
                driver_stmt->bound_column_param_types =
                    reinterpret_cast<pdo_param_type*>(sqlsrv_malloc(stmt->column_count, sizeof(pdo_param_type), 0));
                std::fill(driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
                    PDO_PARAM_STR);
#endif
            }

            for( long i = 0; i < stmt->column_count; ++i ) {
             
#if PHP_VERSION_ID < 80100
                if (NULL== (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
                    (NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {

                    driver_stmt->bound_column_param_types[i] = PDO_PARAM_ZVAL;
                    continue;
                }

                if( bind_data->param_type != PDO_PARAM_ZVAL ) {

                    driver_stmt->bound_column_param_types[i] = bind_data->param_type;
                    bind_data->param_type = PDO_PARAM_ZVAL;
                }
#else
                if (NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
                    (NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
                    continue;
                }

                // TODO: possibly no longer need bound_column_param_types??
                driver_stmt->bound_column_param_types[i] = bind_data->param_type;
#endif
            }
        }

        SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori );
        bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset );

        // support for the PDO rowCount method.  Since rowCount doesn't call a
        // method, PDO relies on us to fill the pdo_stmt_t::row_count member
        // The if condition was changed from 
        // `driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY` 
        // because it caused SQLRowCount to be called at each fetch if using a non-forward cursor
        // which is unnecessary and a performance hit
        if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) {

            stmt->row_count = core::SQLRowCount( driver_stmt );
            driver_stmt->row_count = stmt->row_count;

            // a row_count of -1 means no rows, but we change it to 0
            if( stmt->row_count == -1 ) {

                stmt->row_count = 0;
            }
        }

        // if no data was returned, then return false so data isn't retrieved
        if( !data ) {
            return 0;
        }

        return 1;
    }

    catch( core::CoreException& ) {

        return 0;
    }
    catch( ... ) {
        
        DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred.");
    }
    // Should not have reached here but adding this due to compilation warnings
    return 0;
}

// pdo_sqlsrv_stmt_get_col_data
// Called by the set of PDO Fetch functions.
// Retrieves a single column. PDO driver manager is responsible for freeing the 
// returned buffer. Because PDO can request fields out of order and ODBC does not 
// support out of order field requests, this function should also cache fields.  
// Parameters:
// stmt         - Statement to retrive the column for.
// colno        - Index of the column that needs to be retrieved. Starts with 0.
// ptr          - Returns the buffer containing the column data.
// len          - Length of the buffer returned.
// caller_frees - Flag to let the PDO driver manager know that it is responsible for
//                freeing the memory.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
                                 _Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees)
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result_z, _Inout_ enum pdo_param_type *type)
#endif

{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;
   
    try {

        SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null");

        pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);

        SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null");

        CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX) {
            return 0;
        }

#if PHP_VERSION_ID < 80100
        // Let PDO free the memory after use. 
        * caller_frees = 1;
#endif

        // translate the pdo type to a type the core layer understands
        sqlsrv_phptype sqlsrv_php_type;
        SQLSRV_ASSERT(colno >= 0 && colno < static_cast<int>(driver_stmt->current_meta_data.size()),
            "Invalid column number in pdo_sqlsrv_stmt_get_col_data");

        // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
        // save the php type for next use
        sqlsrv_php_type = driver_stmt->sql_type_to_php_type(
            static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
            static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
            true);
        driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;

        // if a column is bound to a type different than the column type, figure out a way to convert it to the 
        // type they want
#if PHP_VERSION_ID < 80100
        if (stmt->bound_columns && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_ZVAL) {
#else
        if (stmt->bound_columns) {
#endif
            sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type(driver_stmt,
                driver_stmt->bound_column_param_types[colno]
            );

            pdo_bound_param_data* bind_data = NULL;
            bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, colno));
            if (bind_data == NULL) {
                // can't find by index then try searching by name
                bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[colno].name));
            }

            if (bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params)) {

                CHECK_CUSTOM_ERROR(Z_TYPE(bind_data->driver_params) != IS_LONG, driver_stmt,
                    PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1) {
                    throw pdo::PDOException();
                }

                CHECK_CUSTOM_ERROR(driver_stmt->bound_column_param_types[colno] != PDO_PARAM_STR
                    && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_LOB, driver_stmt,
                    PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1) {

                    throw pdo::PDOException();
                }

                sqlsrv_php_type.typeinfo.encoding = Z_LVAL(bind_data->driver_params);

                switch (sqlsrv_php_type.typeinfo.encoding) {
                case SQLSRV_ENCODING_SYSTEM:
                case SQLSRV_ENCODING_BINARY:
                case SQLSRV_ENCODING_UTF8:
                    break;
                default:
                    THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno);
                    break;
                }
            }

            // save the php type for the bound column
            driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
        }

        SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;
#if PHP_VERSION_ID < 80100
        core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast<void**>(ptr)),
            reinterpret_cast<SQLLEN*>(len), true, &sqlsrv_phptype_out);

        if (ptr) {
            zval* zval_ptr = reinterpret_cast<zval*>(sqlsrv_malloc(sizeof(zval)));
            *zval_ptr = convert_to_zval(driver_stmt, sqlsrv_phptype_out, reinterpret_cast<void**>(ptr), *len);
            *ptr = reinterpret_cast<char*>(zval_ptr);
            *len = sizeof(zval);
        }
#else
        SQLLEN len = 0;
        void *ptr = NULL;
        core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, ptr, &len, true, &sqlsrv_phptype_out);
        if (ptr) {
            *result_z = convert_to_zval(driver_stmt, sqlsrv_phptype_out, &ptr, len);
        }
#endif
        return 1;
    }   
    catch ( core::CoreException& ) {
        return 0;
    }
    catch ( ... ) {
        DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred.");
    }
    // Should not have reached here but adding this due to compilation warnings
    return 0;
}

// pdo_sqlsrv_stmt_set_attr
// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement.
// Parameters:
// stmt - Current statement on which the attribute should be set.
// attr - Represents any valid set of attribute constants supported by this driver.
// val - Attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val)
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
    SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_set_attr: driver_data object was null" );

    try {

        switch( attr ) {

            case SQLSRV_ATTR_DIRECT_QUERY:
                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY );
                break;

            case SQLSRV_ATTR_ENCODING:
                set_stmt_encoding( driver_stmt, val );
                break;
            
            case PDO_ATTR_CURSOR:
                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY );
                break;
           
            case SQLSRV_ATTR_QUERY_TIMEOUT:
                core_sqlsrv_set_query_timeout( driver_stmt, val );
                break;

            case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY );
                break;

            case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
                core_sqlsrv_set_buffered_query_limit( driver_stmt, val );
                break;

            case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
                driver_stmt->fetch_numeric = zend_is_true(val);
                break;

            case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
                driver_stmt->fetch_datetime = zend_is_true(val);
                break;

            case SQLSRV_ATTR_FORMAT_DECIMALS:
                driver_stmt->format_decimals = zend_is_true(val);
                break;

            case SQLSRV_ATTR_DECIMAL_PLACES:
                core_sqlsrv_set_decimal_places(driver_stmt, val);
                break;

            case SQLSRV_ATTR_DATA_CLASSIFICATION:
                driver_stmt->data_classification = zend_is_true(val);
                break;

            default:
                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
                break;
        }
    }
    catch( core::CoreException& ) {
        return 0;
    }

    catch ( ... ) {
        DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred.");
    }

    return 1;
}


// pdo_sqlsrv_stmt_get_attr
// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement.
// Parameters:
// stmt - Current statement for which the attribute value is requested.
// attr - Represents any valid set of attribute constants supported by this driver.
// return_value - Attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value )
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;
    
    pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
    SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" );

    try {

        switch( attr ) {

            case SQLSRV_ATTR_DIRECT_QUERY:
            {
                ZVAL_BOOL( return_value, driver_stmt->direct_query );
                break;
            }

           case SQLSRV_ATTR_ENCODING:
            { 
                ZVAL_LONG( return_value, driver_stmt->encoding() );
                break;
            }
            
            case PDO_ATTR_CURSOR:
            {
                ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ? 
                                           PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY )); 
                break;
            }

            case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
            {
                ZVAL_LONG( return_value, driver_stmt->cursor_type ); 
                break;
            }

            case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
            {
                ZVAL_LONG( return_value, driver_stmt->buffered_query_limit ); 
                break;
            }

            case SQLSRV_ATTR_QUERY_TIMEOUT:
            {
                ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout ));
                break;
            }

            case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
            {
                ZVAL_BOOL( return_value, driver_stmt->fetch_numeric );
                break;
            }

            case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
            {
                ZVAL_BOOL( return_value, driver_stmt->fetch_datetime );
                break;
            }

            case SQLSRV_ATTR_FORMAT_DECIMALS:
            {
                ZVAL_BOOL( return_value, driver_stmt->format_decimals );
                break;
            }

            case SQLSRV_ATTR_DECIMAL_PLACES:
            {
                ZVAL_LONG( return_value, driver_stmt->decimal_places ); 
                break;
            }

            case SQLSRV_ATTR_DATA_CLASSIFICATION:
            {
                ZVAL_BOOL(return_value, driver_stmt->data_classification);
                break;
            }

            default:
                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
                break;
        }
    }
    catch( core::CoreException& ) {

        return 0;
    }
    catch ( ... ) {

        DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred.");
    }

    return 1;
}

// pdo_sqlsrv_stmt_get_col_meta
// Maps to PDOStatement::getColumnMeta. Return extra metadata.  
// Though we don't return any extra metadata, PDO relies on us to 
// create the associative array that holds the standard information, 
// so we create one and return it for PDO's use.
// Parameters:
// stmt         - Current statement.
// colno        - The index of the field for which to return the metadata.
// return_value - zval* consisting of the metadata.
// Return:
// FAILURE for failure, SUCCESS for success.
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value)
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    try {
        SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" );
        SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value.  Must be NULL." );

        sqlsrv_stmt* driver_stmt = static_cast<sqlsrv_stmt*>( stmt->driver_data );
        SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: stmt->driver_data was null");

        // Based on PDOStatement::getColumnMeta API, this should return FALSE 
        // if the requested column does not exist in the result set, or if 
        // no result set exists. Thus, do not use SQLSRV_ASSERT, which causes 
        // the script to fail right away. Instead, log this warning if logging
        // is enabled
        if (colno < 0 || colno >= stmt->column_count || stmt->columns == NULL) {
            LOG( SEV_WARNING, "Invalid column number %1!d!", colno );
            return FAILURE;
        }

        // initialize the array to nothing, as PDO requires us to create it
        array_init(return_value);

        field_meta_data* core_meta_data;

        // metadata should have been saved earlier
        SQLSRV_ASSERT(colno < driver_stmt->current_meta_data.size(), "pdo_sqlsrv_stmt_get_col_meta: Metadata vector out of sync with column numbers");
        core_meta_data = driver_stmt->current_meta_data[colno];
        
        // add the following fields: flags, native_type, driver:decl_type, table
        if (driver_stmt->data_classification) {
            core_sqlsrv_sensitivity_metadata(driver_stmt);

            // initialize the column data classification array 
            zval data_classification;
            ZVAL_UNDEF(&data_classification);
            array_init(&data_classification);

            data_classification::fill_column_sensitivity_array(driver_stmt, (SQLSMALLINT)colno, &data_classification);

            add_assoc_zval(return_value, "flags", &data_classification);
        }
        else {
            add_assoc_long(return_value, "flags", 0);
        }

        // get the name of the data type
        char field_type_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'};
        SQLSMALLINT out_buff_len;
        SQLLEN not_used;
        core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name,
                               sizeof( field_type_name ), &out_buff_len, &not_used );
        add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name );

        // get the PHP type of the column.  The types returned here mirror the types returned by debug_zval_dump when 
        // given a variable of the same type.  However, debug_zval_dump also gives the length of a string, and we only
        // say string, since the length is given in another field of the metadata array.
        long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type );
        switch( pdo_type ) {
            case PDO_PARAM_STR:
            {
                //Declarations eliminate compiler warnings about string constant to char* conversions
                std::string key = "native_type";
                std::string str = "string";
                add_assoc_string( return_value, &key[0], &str[0] );
            }
                break;
            default:
                DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" );
                break;
        }

        // add the table name of the field.  All the tests so far show this to always be "", but we adhere to the PDO spec
        char table_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'};
        SQLLEN field_type_num;
        core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX,
                               &out_buff_len, &field_type_num );
        add_assoc_string( return_value, "table", table_name );

#if PHP_VERSION_ID < 80100
        if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) {
            add_assoc_long( return_value, "pdo_type", pdo_type );
        }
#else
        if (stmt->columns) {
            add_assoc_long(return_value, "pdo_type", pdo_type);
        }
#endif
    }
    catch( core::CoreException& ) {
        zval_ptr_dtor(return_value);
        return FAILURE;
    }
    catch(...) {
        zval_ptr_dtor(return_value);
        DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." );
    }
    
    return SUCCESS;
}


// pdo_sqlsrv_stmt_next_rowset
// Maps to PDOStatement::nextRowset.
// Move the cursor to the beginning of the next rowset in a multi-rowset result.
// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data.  
// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data.
// If another result set is available, this function returns 1.  Otherwise it returns 0.
// Parameters:
// stmt - PDOStatement object containing the result set.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt )
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    try {

        SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" );

        pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );

        SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" );

        core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) );

        // clear the current meta data since the new result will generate new meta data
        driver_stmt->clean_up_results_metadata();

        // if there are no more result sets, return that it failed.
        if( driver_stmt->past_next_result_end == true ) {
            return 0;
        }

        stmt->column_count = core::SQLNumResultCols( driver_stmt );

        // return the row count regardless if there are any rows or not
        stmt->row_count = core::SQLRowCount( driver_stmt );
        
        driver_stmt->column_count = stmt->column_count;
        driver_stmt->row_count = stmt->row_count;
    }
    catch( core::CoreException& ) {

        return 0;
    }
    catch( ... ) {

        DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." );
    }

    return 1;
}

// pdo_sqlsrv_stmt_param_hook
// Maps to PDOStatement::bindColumn.
// Called by PDO driver manager to bind a parameter or column. 
// This function pulls several duties for binding parameters and columns.  
// It takes an event as a parameter that explains what the function should do on 
// behalf of a parameter or column.  We only use one of these events, 
// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return.
// Paramters:
// stmt - PDO Statement object to bind a parameter.
// param - paramter to bind.
// event_type - Event to bind a parameter
// Return: 
// Returns 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
                               _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type)
{
    PDO_RESET_STMT_ERROR;

    try {

        switch( event_type ) {

            // since the param isn't reliable, we don't do anything here
            case PDO_PARAM_EVT_ALLOC:
                {
                    pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
                    if (driver_stmt->conn->ce_option.enabled) {
                        if (driver_stmt->direct_query) {
                            THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED);
                        }
                        if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
                            THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
                        }
                    }
                    if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && (param->param_type & PDO_PARAM_INPUT_OUTPUT)) {
                        THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
                    }
                }
                break;
            case PDO_PARAM_EVT_FREE:
                break;
            // bind the parameter in the core layer
            case PDO_PARAM_EVT_EXEC_PRE:
                {
                    PDO_VALIDATE_STMT;
                    PDO_LOG_STMT_ENTRY;

                    // skip column bindings
                    if( !param->is_param ) {
                        break;
                    }

                    sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
                    SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" );

                    // prepare for binding parameters by flushing anything remaining in the result set
                    if( driver_stmt->executed && !driver_stmt->past_next_result_end ) {

                        while( driver_stmt->past_next_result_end == false ) {

                            core_sqlsrv_next_result( driver_stmt, false );
                        }
                    }

                    int direction = SQL_PARAM_INPUT;
                    SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
                    SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
                    SQLSMALLINT decimal_digits = 0;
                    // determine the direction of the parameter.  By default it's input, but if the user specifies a size
                    // that means they want output, and if they include the flag, then it's input/output.
                    // It's invalid to specify the input/output flag but not specify a length
                    CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0),
                                        driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
                        throw pdo::PDOException();
                    }
                    // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
                    // and the SQLSRV_PHPTYPE_* constant
                    // vso 2829: derive the pdo_type for input/output parameter as well
                    // also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params
                    int pdo_type = param->param_type;
                    if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
                        if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
                            direction = SQL_PARAM_INPUT_OUTPUT;
                            pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
                        }
                        else {
                            direction = SQL_PARAM_OUTPUT;
                        }
                    }

                    // check if the user has specified the character set to use, take it off but ignore 
#if PHP_VERSION_ID >= 70200
                    if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
                        pdo_type = pdo_type & ~PDO_PARAM_STR_NATL;
                        LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored.");
                    }
                    if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
                        pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR;
                        LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored.");
                    }
#endif

                    // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
                    // and the SQLSRV_PHPTYPE_* constant
                    SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
                    switch (pdo_type) {
                        case PDO_PARAM_BOOL:
                        case PDO_PARAM_INT:
                            php_out_type = SQLSRV_PHPTYPE_INT;
                            break;
                        case PDO_PARAM_STR:
                            php_out_type = SQLSRV_PHPTYPE_STRING;
                            break;
                        // when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is
                        // since the core layer keys off the zval type, we substitute a null for what they gave us
                        case PDO_PARAM_NULL:
                        {
                            zval null_zval;
                            php_out_type = SQLSRV_PHPTYPE_NULL;
                            
                            ZVAL_NULL( &null_zval );
                            zval_ptr_dtor( &param->parameter );
                            param->parameter = null_zval;
                            break;
                        }
                        case PDO_PARAM_LOB:
                            php_out_type = SQLSRV_PHPTYPE_STREAM;
                            break;
                        case PDO_PARAM_STMT:
                            THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
                            break;
                        default:
                            SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." );
                            break;
                    }
                    // set the column size parameter for bind_param if we are expecting something back
                    if( direction != SQL_PARAM_INPUT ) {
                        switch( php_out_type ) {
                            case SQLSRV_PHPTYPE_NULL:
                            case SQLSRV_PHPTYPE_STREAM:
                            {
                                zval *zv = &param->parameter;
                                if (Z_ISREF_P(zv)) {
                                    ZVAL_DEREF(zv);
                                }
                                // Table-valued parameters are input-only
                                CHECK_CUSTOM_ERROR(Z_TYPE_P(zv) == IS_ARRAY, driver_stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
                                    throw pdo::PDOException();
                                }
                                // For other types, simply throw the following error
                                THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
                                break;
                            }
                            case SQLSRV_PHPTYPE_INT:
                                column_size = SQLSRV_UNKNOWN_SIZE;
                                break;
                            case SQLSRV_PHPTYPE_STRING:
                            {
                                CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt, 
                                                    PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) {
                                    throw pdo::PDOException();
                                }
                                column_size = param->max_value_len;
                                break;
                            }
                            default:
                                SQLSRV_ASSERT( false, "Invalid PHP type for output parameter.  Should have been caught already." );
                                break;
                        }
                    }
                    // block all objects from being bound as input or input/output parameters since there is a
                    // weird case: 
                    // $obj = date_create(); 
                    // $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR
                    // that succeeds since the core layer implements DateTime object handling for the sqlsrv
                    // 2.0 driver.  To be consistent and avoid surprises of one object type working and others
                    // not, we block all objects here.
                    CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT,
                                        driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
                        throw pdo::PDOException();
                    }

                    // the encoding by default is that set on the statement
                    SQLSRV_ENCODING encoding = driver_stmt->encoding();
                    // if the statement's encoding is the default, then use the one on the connection
                    if( encoding == SQLSRV_ENCODING_DEFAULT ) {
                        encoding = driver_stmt->conn->encoding();
                    }

                    // Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL
                    // But this extended type will be ignored in real prepared statements, so the encoding deliberately 
                    // set in the statement or driver options will still take precedence
                    if( !Z_ISUNDEF(param->driver_params) ) {
                        CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, 
                                            PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
                            throw pdo::PDOException();
                        }
                        CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt,
                                            PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) {
                            throw pdo::PDOException();
                        }
                        encoding = static_cast<SQLSRV_ENCODING>( Z_LVAL( param->driver_params ));

                        switch( encoding ) {
                            case SQLSRV_ENCODING_SYSTEM:
                            case SQLSRV_ENCODING_BINARY:
                            case SQLSRV_ENCODING_UTF8:
                                break;
                            default:
                                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
                                                 param->paramno + 1 );
                                break;
                        }
                    }

                    // and bind the parameter
                    core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
                                            sql_type, column_size, decimal_digits);
                }
                break;
            // undo any work done by the core layer after the statement is executed
            case PDO_PARAM_EVT_EXEC_POST:
                {
                    PDO_VALIDATE_STMT;
                    PDO_LOG_STMT_ENTRY;
                }

                break;
            case PDO_PARAM_EVT_FETCH_PRE:
                break;
            case PDO_PARAM_EVT_FETCH_POST:
                break;
            case PDO_PARAM_EVT_NORMALIZE:
                break;
            default:
                DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" );
                break;
        }
    }
    catch( core::CoreException& ) {

        return 0;
    }
    catch( ... ) {

        DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" );
    }

    return 1;
}


// Returns a sqlsrv_phptype for a given SQL Server data type.
sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_over_stream )
{
    sqlsrv_phptype sqlsrv_phptype;
    int local_encoding = this->encoding();
    // if the encoding on the connection changed
    if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) {
        local_encoding = conn->encoding();
        SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID,
                       "Invalid encoding on the connection.  Must not be invalid or default." );
    }                

    sqlsrv_phptype.typeinfo.encoding = local_encoding;

    switch( sql_type ) {
        case SQL_BIT:
        case SQL_INTEGER:
        case SQL_SMALLINT:
        case SQL_TINYINT:
            if ( this->fetch_numeric ) {
                sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
            }
            else {
                sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
                sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; 
            }
            break;
        case SQL_FLOAT:
        case SQL_REAL:
            if ( this->fetch_numeric ) {
                sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
            }
            else {
                sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
                sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; 
            }
            break;
        case SQL_TYPE_DATE:
        case SQL_SS_TIMESTAMPOFFSET:
        case SQL_SS_TIME2:
        case SQL_TYPE_TIMESTAMP:
            if ( this->fetch_datetime ) {
                sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
            }
            else {
                sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
            }
            break;
        case SQL_BIGINT:
        case SQL_DECIMAL:
        case SQL_NUMERIC:
            sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
            sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; 
            break;
        case SQL_CHAR:
        case SQL_GUID:
        case SQL_WCHAR:
        case SQL_VARCHAR:
        case SQL_WVARCHAR:
        case SQL_LONGVARCHAR:
        case SQL_WLONGVARCHAR:
        case SQL_SS_XML:
        case SQL_SS_VARIANT:
            sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
            break;
        case SQL_BINARY:
        case SQL_LONGVARBINARY:
        case SQL_VARBINARY:
        case SQL_SS_UDT:
            sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
            sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
            break;
        default:
            sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
            sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
            break;
    }
    
    return sqlsrv_phptype;
}