diff --git a/pdo_sqlsrv/CREDITS b/pdo_sqlsrv/CREDITS new file mode 100644 index 00000000..2122944a --- /dev/null +++ b/pdo_sqlsrv/CREDITS @@ -0,0 +1 @@ +Microsoft Drivers 4.0.0 for PHP for SQL Server (PDO driver) diff --git a/pdo_sqlsrv/config.w32 b/pdo_sqlsrv/config.w32 new file mode 100644 index 00000000..8f81ae16 --- /dev/null +++ b/pdo_sqlsrv/config.w32 @@ -0,0 +1,41 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: config.w32 +// +// Contents: JScript build configuration used by buildconf.bat +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no"); + +if( PHP_PDO_SQLSRV != "no" ) { + + pdo_sqlsrv_src = "pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; + + if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") && + CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")) { + + EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") + + CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_PDO_SQLSRV_ODBC'); + CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_PDO_SQLSRV_ODBC'); + ADD_FLAG( 'LDFLAGS_PDO_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); + ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/EHsc' ); + ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/GS' ); + ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/Zi' ); + ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); + ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); + } + +} diff --git a/pdo_sqlsrv/core_conn.cpp b/pdo_sqlsrv/core_conn.cpp new file mode 100644 index 00000000..89f213b4 --- /dev/null +++ b/pdo_sqlsrv/core_conn.cpp @@ -0,0 +1,771 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_conn.cpp +// +// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include +#include +#include +#include + +#include +#include + +// *** internal variables and constants *** + +namespace { + +// *** internal constants *** +// an arbitrary figure that should be large enough for most connection strings. +const int DEFAULT_CONN_STR_LEN = 2048; + +// length of buffer used to retrieve information for client and server info buffers +const int INFO_BUFFER_LEN = 256; + +// processor architectures +const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; + +// ODBC driver name. +const char CONNECTION_STRING_DRIVER_NAME[] = "Driver={ODBC Driver 11 for SQL Server};"; + +// default options if only the server is specified +const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; + +// connection option appended when no user name or password is given +const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; + +// connection option appended for MARS when MARS isn't explicitly mentioned +const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; + +// *** internal function prototypes *** + +void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, + HashTable* options_ht, const connection_option valid_conn_opts[], + void* driver,__inout std::string& connection_string TSRMLS_DC ); +void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); +const char* get_processor_arch( void ); +void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); +connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); + +} + +// core_sqlsrv_connect +// opens a connection and returns a sqlsrv_conn structure. +// Parameters: +// henv_cp - connection pooled env context +// henv_ncp - non connection pooled env context +// server - name of the server we're connecting to +// uid - username +// pwd - password +// options_ht - zend_hash list of options +// err - error callback to put into the connection's context +// valid_conn_opts[] - array of valid driver supported connection options. +// driver - reference to caller +// Return +// A sqlsrv_conn structure. An exception is thrown if an error occurs + +sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, + const char* server, const char* uid, const char* pwd, + HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], + void* driver, const char* driver_func TSRMLS_DC ) + +{ + SQLRETURN r; + std::string conn_str; + conn_str.reserve( DEFAULT_CONN_STR_LEN ); + sqlsrv_malloc_auto_ptr conn; + sqlsrv_malloc_auto_ptr wconn_string; + unsigned int wconn_len = 0; + + try { + + sqlsrv_context* henv = &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 + // we do this earlier because we have to allocate the connection handle prior to setting attributes on + // it in build_connection_string_and_set_conn_attr. + + if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + + zval* option_z = NULL; + int zr = SUCCESS; + + option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); + if (option_z) { + + // if the option was found and it's not true, then use the non pooled environment handle + if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { + + henv = &henv_ncp; + } + } + } + + SQLHANDLE temp_conn_h; + core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); + + conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); + conn->set_func( driver_func ); + + + build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, + conn_str TSRMLS_CC ); + + // We only support UTF-8 encoding for connection string. + // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW + wconn_len = (unsigned int) (conn_str.length() + 1) * sizeof( wchar_t ); + + wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), (unsigned int) conn_str.length(), &wconn_len ); + CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message() ) + { + throw core::CoreException(); + } + + SQLSMALLINT output_conn_size; + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), + static_cast( wconn_len ), NULL, + 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + // clear the connection string from memory to remove sensitive data (such as a password). + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + conn_str.clear(); + + if( !SQL_SUCCEEDED( r )) { + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); + // if it's a IM002, meaning that the correct ODBC driver is not installed + CHECK_CUSTOM_ERROR( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && + state[4] == '2', conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) { + throw core::CoreException(); + } + } + CHECK_SQL_ERROR( r, conn ) { + throw core::CoreException(); + } + + CHECK_SQL_WARNING_AS_ERROR( r, conn ) { + throw core::CoreException(); + } + + // determine the version of the server we're connected to. The server version is left in the + // connection upon return. + determine_server_version( conn TSRMLS_CC ); + + } + catch( std::bad_alloc& ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + conn->invalidate(); + DIE( "C++ memory allocation failure building the connection string." ); + } + catch( std::out_of_range const& ex ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); + conn->invalidate(); + throw; + } + catch( std::length_error const& ex ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); + conn->invalidate(); + throw; + } + catch( core::CoreException& ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + conn->invalidate(); + throw; + } + + sqlsrv_conn* return_conn = conn; + conn.transferred(); + + return return_conn; +} + + + +// core_sqlsrv_begin_transaction +// Begins a transaction on a specified connection. The current transaction +// includes all statements on the specified connection that were executed after +// the call to core_sqlsrv_begin_transaction and before any calls to +// core_sqlsrv_rollback or core_sqlsrv_commit. +// The default transaction mode is auto-commit. This means that all queries +// are automatically committed upon success unless they have been designated +// as part of an explicit transaction by using core_sqlsrv_begin_transaction. +// Parameters: +// sqlsrv_conn*: The connection with which the transaction is associated. + +void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_commit +// 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 +// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or +// core_sqlsrv_commit. +// Parameters: +// sqlsrv_conn*: The connection on which the transaction is active. + +void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); + + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_rollback +// 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 +// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or +// core_sqlsrv_commit. +// Parameters: +// sqlsrv_conn*: The connection on which the transaction is active. + +void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); + + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), + SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_close +// Called when a connection resource is destroyed by the Zend engine. +// Parameters: +// conn - The current active connection. +void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) +{ + // if the connection wasn't successful, just return. + if( conn == NULL ) + return; + + try { + + // rollback any transaction in progress (we don't care about the return result) + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + } + catch( core::CoreException& ) { + LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); + } + + // disconnect from the server + SQLRETURN r = SQLDisconnect( conn->handle() ); + if( !SQL_SUCCEEDED( r )) { + LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); + } + + // free the connection handle + conn->invalidate(); + + sqlsrv_free( conn ); +} + +// core_sqlsrv_prepare +// Create a statement object and prepare the SQL query passed in for execution at a later time. +// Parameters: +// stmt - statement to be prepared +// sql - T-SQL command to prepare +// sql_len - length of the T-SQL string + +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) +{ + try { + + // convert the string from its encoding to UTf-16 + // if the string is empty, we initialize the fields and skip since an empty string is a + // failure case for utf16_string_from_mbcs_string + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len = 0; + if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { + wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); + wsql_string[0] = L'\0'; + wsql_len = 0; + } + else { + + if (sql_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : + stmt->encoding() ); + wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), + static_cast( sql_len ), &wsql_len ); + CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + get_last_error_message() ) { + throw core::CoreException(); + } + } + + // prepare our wide char query string + core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); + } + catch( core::CoreException& ) { + + throw; + } +} + +// core_sqlsrv_get_server_version +// Determines the vesrion of the SQL Server we are connected to. Calls a helper function +// get_server_version to get the version of SQL Server. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// *server_version - zval for returning results. + +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); + if (NULL != buffer) { + sqlsrv_free( buffer ); + } + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_get_server_info +// Returns the Database name, the name of the SQL Server we are connected to +// and the version of the SQL Server. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// *server_info - zval for returning results. + +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + // initialize the array + core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); + + // Get the database name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the server version + get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the server name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_get_client_info +// Returns the ODBC driver's dll name, version and the ODBC version. +// Parameters +// conn - The connection resource by which the client and server are connected. +// *client_info - zval for returning the results. + +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + // initialize the array + core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); + + // Get the ODBC driver's dll name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the ODBC driver's ODBC version + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the OBDC driver's version + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + } + + catch( core::CoreException& ) { + throw; + } +} + + +// core_is_conn_opt_value_escaped +// determine if connection string value is properly escaped. +// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that +// the value will be surrounded by { and } by the caller after it has been validated + +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) +{ + // 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 -= 2; + } + // check to make sure that all right braces are escaped + size_t i = 0; + while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { + // skip both braces + if( value[i] == '}' ) + ++i; + ++i; + } + if( i < value_len && value[i] == '}' ) { + return false; + } + + return true; +} + + +// *** internal connection functions and classes *** + +namespace { + +connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, + const connection_option conn_opts[] TSRMLS_DC ) +{ + for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { + + if( key == conn_opts[ opt_idx ].conn_option_key ) { + + return &conn_opts[ opt_idx ]; + } + } + + SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); + return NULL; // avoid a compiler warning +} + +// 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. + +void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, + HashTable* options, const connection_option valid_conn_opts[], + void* driver,__inout std::string& connection_string TSRMLS_DC ) +{ + bool credentials_mentioned = false; + bool mars_mentioned = false; + connection_option const* conn_opt; + int zr = SUCCESS; + + try { + + connection_string = CONNECTION_STRING_DRIVER_NAME; + + // Add the server name + common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); + + // if uid is not present then we use trusted connection. + if(uid == NULL || strlen( uid ) == 0 ) { + + connection_string += "Trusted_Connection={Yes};"; + } + else { + + bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); + CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + throw core::CoreException(); + } + + common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC ); + + // if no password was given, then don't add a password to the connection string. Perhaps the UID + // given doesn't have a password? + if( pwd != NULL ) { + + escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd )); + CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + + throw core::CoreException(); + } + + common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); + } + } + + // if no options were given, then we set MARS the defaults and return immediately. + if( options == NULL || zend_hash_num_elements( options ) == 0 ) { + connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; + return; + } + + // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file + // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" + // flag is set to false. + if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { + + zval* trace_value = NULL; + trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); + + if (trace_value == NULL || !zend_is_true(trace_value)) { + + zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); + } + } + + for( zend_hash_internal_pointer_reset( options ); + zend_hash_has_more_elements( options ) == SUCCESS; + zend_hash_move_forward( options )) { + + int type = HASH_KEY_NON_EXISTENT; + zend_string *key = NULL; + zend_ulong index = 0; + zval* data = NULL; + + type = zend_hash_get_current_key( options, &key, &index ); + + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); + + core::sqlsrv_zend_hash_get_current_data( *conn, options, data TSRMLS_CC ); + + conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + + if( index == SQLSRV_CONN_OPTION_MARS ) { + mars_mentioned = true; + } + + conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + } + + // MARS on if not explicitly turned off + if( !mars_mentioned ) { + connection_string += CONNECTION_OPTION_MARS_ON; + } + + } + catch( core::CoreException& ) { + throw; + } +} + + +// get_server_version +// Helper function which returns the version of the SQL Server we are connected to. + +void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + *server_version = buffer; + len = buffer_len; + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + + +// get_processor_arch +// Calls GetSystemInfo to verify the what architecture of the processor is supported +// and return the string of the processor name. +const char* get_processor_arch( void ) +{ + SYSTEM_INFO sys_info; + GetSystemInfo( &sys_info); + switch( sys_info.wProcessorArchitecture ) { + + case PROCESSOR_ARCHITECTURE_INTEL: + return PROCESSOR_ARCH[0]; + + case PROCESSOR_ARCHITECTURE_AMD64: + return PROCESSOR_ARCH[1]; + + case PROCESSOR_ARCHITECTURE_IA64: + return PROCESSOR_ARCH[2]; + + default: + DIE( "Unknown Windows processor architecture." ); + return NULL; + } +} + + +// some features require a server of a certain version or later +// this function determines the version of the server we're connected to +// and stores it in the connection. Any errors are logged before return. +// Exception is thrown when the server version is either undetermined +// or is invalid (< 2000). + +void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) +{ + SQLSMALLINT info_len; + char p[ INFO_BUFFER_LEN ]; + core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); + + errno = 0; + char version_major_str[ 3 ]; + SERVER_VERSION version_major; + memcpy( version_major_str, p, 2 ); + version_major_str[ 2 ] = '\0'; + version_major = static_cast( atoi( version_major_str )); + + CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) + { + throw core::CoreException(); + } + + // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least + // that high + conn->server_version = version_major; +} + +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) +{ + // wrap a connection option in a quote. It is presumed that any character that need to be escaped will + // be escaped, such as a closing }. + TSRMLS_C; + + if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { + ++val; + val_len -= 2; + } + conn_str += odbc_name; + conn_str += "={"; + conn_str.append( val, val_len ); + conn_str += "};"; +} + +} // namespace + +// simply add the parsed value to the connection string +void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str + TSRMLS_DC ) +{ + const char* val_str = Z_STRVAL_P( value ); + size_t val_len = Z_STRLEN_P( value ); + common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); +} + +// do nothing for connection pooling since we handled it earlier when +// deciding which environment handle to use. +void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ + TSRMLS_DC ) +{ + TSRMLS_C; +} + +// helper function to evaluate whether a string value is true or false. +// Values = ("true" or "1") are treated as true values. Everything else is treated as false. +// Returns 1 for true and 0 for false. + +size_t core_str_zval_is_true( zval* value_z ) +{ + SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); + + char* value_in = Z_STRVAL_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); + + // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) + size_t last_char = val_len - 1; + while( isspace( value_in[ last_char ] )) { + value_in[ last_char ] = '\0'; + val_len = last_char; + --last_char; + } + + // save adjustments to the value made by stripping whitespace at the end + core::sqlsrv_zval_stringl( value_z, value_in, val_len); + + const char VALID_TRUE_VALUE_1[] = "true"; + const char VALID_TRUE_VALUE_2[] = "1"; + + if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || + ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) + ) { + + return 1; // true + } + + return 0; // false +} diff --git a/pdo_sqlsrv/core_init.cpp b/pdo_sqlsrv/core_init.cpp new file mode 100644 index 00000000..7efeafa3 --- /dev/null +++ b/pdo_sqlsrv/core_init.cpp @@ -0,0 +1,175 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_init.cpp +// +// Contents: common initialization routines shared by PDO and sqlsrv +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + + +// module global variables (initialized in minit and freed in mshutdown) +HMODULE g_sqlsrv_hmodule = NULL; +OSVERSIONINFO g_osversion; + + +// core_sqlsrv_minit +// Module initialization +// This function is called once per execution by the driver layer's MINIT function. +// The primary responsibility of this function is to allocate the two environment +// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC +// connection handle. +// Parameters: +// henv_cp - Environment handle for pooled connection. +// henv_ncp - Environment handle for non-pooled connection. +// err - Driver specific error handler which handles any errors during initialization. +void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) +{ + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); + + *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL + + try { + + // get the version of the OS we're running on. For now this governs certain flags used by + // WideCharToMultiByte. It might be relevant to other things in the future. + g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); + BOOL ver_return = GetVersionEx( &g_osversion ); + if( !ver_return ) { + LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); + throw core::CoreException(); + } + + SQLHANDLE henv = SQL_NULL_HANDLE; + SQLRETURN r; + + // allocate the non pooled environment handle + // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so + // we use the direct ODBC function. + r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); + if( !SQL_SUCCEEDED( r )) { + throw core::CoreException(); + } + + *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); + (*henv_ncp)->set_func( driver_func ); + + // set to ODBC 3 + core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER + TSRMLS_CC ); + + // disable connection pooling + core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), + SQL_IS_UINTEGER TSRMLS_CC ); + + // allocate the pooled envrionment handle + // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so + // we use the direct ODBC function. + r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); + if( !SQL_SUCCEEDED( r )) { + throw core::CoreException(); + } + + *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); + (*henv_cp)->set_func( driver_func ); + + // set to ODBC 3 + core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); + + // enable connection pooling + core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), + SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch( core::CoreException& e ) { + + LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); + + if( *henv_ncp != NULL ) { + // free the ODBC env handle allocated just above + SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); + delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) + *henv_ncp = NULL; + } + if( *henv_cp != NULL ) { + // free the ODBC env handle allocated just above + SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); + delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) + *henv_cp = NULL; + } + + throw e; // rethrow for the driver to catch + } + catch( std::bad_alloc& e ) { + + LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); + + if( *henv_ncp != NULL ) { + SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); + delete *henv_ncp; + *henv_ncp = NULL; + } + if( *henv_cp ) { + SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); + delete *henv_cp; + *henv_cp = NULL; + } + + throw e; // rethrow for the driver to catch + } +} + +// core_sqlsrv_mshutdown +// 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. +// Parameters: +// henv_cp - Pooled environment handle. +// henv_ncp - Non-pooled environment handle. +void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) +{ + if( henv_ncp != SQL_NULL_HANDLE ) { + + henv_ncp.invalidate(); + delete &henv_ncp; + } + + if( henv_cp != SQL_NULL_HANDLE ) { + + henv_cp.invalidate(); + delete &henv_cp; + } + + return; +} + + +// DllMain for the extension. + +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) +{ + switch( fdwReason ) { + case DLL_PROCESS_ATTACH: + // store the module handle for use by client_info and server_info + g_sqlsrv_hmodule = hinstDLL; + break; + default: + break; + } + + return TRUE; +} diff --git a/pdo_sqlsrv/core_results.cpp b/pdo_sqlsrv/core_results.cpp new file mode 100644 index 00000000..befa124e --- /dev/null +++ b/pdo_sqlsrv/core_results.cpp @@ -0,0 +1,1342 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_results.cpp +// +// Contents: Result sets +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include +#include +#include + +using namespace core; + +// conversion matrix +// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported +// this is initialized the first time the buffered result set is created. +sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; + +namespace { + +// *** internal types *** + +#pragma warning(disable:4200) + +// *** internal constants *** + +const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field + +// *** internal functions *** + +// return an integral type rounded up to a certain number +template +T align_to( T number ) +{ + DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); + return ((number % align) == 0) ? number : (number + align - (number % align)); +} + +// return a pointer address aligned to a certain address boundary +template +T* align_to( T* ptr ) +{ + size_t p_value = (size_t) ptr; + return align_to( p_value ); +} + +// set the nth bit of the bitstream starting at ptr +void set_bit( void* ptr, unsigned int bit ) +{ + unsigned char* null_bits = reinterpret_cast( ptr ); + null_bits += bit >> 3; + *null_bits |= 1 << ( 7 - ( bit & 0x7 )); +} + +// retrieve the nth bit from the bitstream starting at ptr +bool get_bit( void* ptr, unsigned int bit ) +{ + unsigned char* null_bits = reinterpret_cast( ptr ); + null_bits += bit >> 3; + return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); +} + +// read in LOB field during buffered result creation +SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, + zend_long mem_used TSRMLS_DC ); + +// dtor for each row in the cache +void cache_row_dtor(zval* data); + +// convert a number to a string using locales +// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of +// subclassing a new streambuf just to avoid the copy, it's easier to do the copy +template +SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + sqlsrv_error_auto_ptr& last_error ) +{ + std::basic_ostringstream os; + std::locale loc; + os.imbue( loc ); + std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); + std::basic_string& str_num = os.str(); + + if( os.fail() ) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( + (SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); + return SQL_ERROR; + } + + if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( + (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); + return SQL_ERROR; + } + + *out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator + memcpy( buffer, str_num.c_str(), *out_buffer_length ); + + return SQL_SUCCESS; +} + +template +SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +{ + Number* number_data = reinterpret_cast( buffer ); + std::locale loc; // default locale should match system + std::basic_istringstream is; + is.str( string_data ); + is.imbue( loc ); + std::ios_base::iostate st = 0; + + std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf( ) ), + std::basic_istream::_Iter(0), is, st, *number_data ); + + if( st & std::ios_base::failbit ) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( + (SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 ); + return SQL_ERROR; + } + + *out_buffer_length = sizeof( Number ); + + return SQL_SUCCESS; +} + +// "closure" for the hash table destructor +struct row_dtor_closure { + + sqlsrv_buffered_result_set* results; + BYTE* row_data; + + row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : + results( st ), row_data( row ) + { + } +}; + +sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) +{ + SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; + SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; + SQLINTEGER native_code; + SQLSMALLINT wnative_message_len = 0; + + SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, + SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); + if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + return NULL; + } + + // convert the error into the encoding of the context + SQLSRV_ENCODING enc = odbc->encoding(); + if( enc == SQLSRV_ENCODING_DEFAULT ) { + enc = odbc->conn->encoding(); + } + + // convert the error into the encoding of the context + sqlsrv_malloc_auto_ptr sql_state; + SQLLEN sql_state_len = 0; + if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { + return NULL; + } + + sqlsrv_malloc_auto_ptr native_message; + SQLLEN native_message_len = 0; + if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { + return NULL; + } + + return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, + native_code ); +} + +} // namespace + +// base class result set + +sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : + odbc( stmt ) +{ +} + + +// ODBC result set +// This object simply wraps ODBC function calls + +sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : + sqlsrv_result_set( stmt ) +{ +} + +sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) +{ +} + +SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); +} + +SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); +} + +SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, + out_buffer_length TSRMLS_CC ); +} + +sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return odbc_get_diag_rec( odbc, record_number ); +} + +SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLRowCount( odbc TSRMLS_CC ); +} + + +// Buffered result set +// This class holds a result set in memory + +sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : + sqlsrv_result_set( stmt ), + cache(NULL), + col_count(0), + meta(NULL), + current(0), + last_field_index(-1), + read_so_far(0) +{ + // 10 is an arbitrary number for now for the initial size of the cache + ALLOC_HASHTABLE( cache ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); + col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); + // there is no result set to buffer + if( col_count == 0 ) { + return; + } + + SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags + meta = static_cast( sqlsrv_malloc( col_count * + sizeof( sqlsrv_buffered_result_set::meta_data ))); + + // set up the conversion matrix if this is the first time we're called + if( conv_matrix.size() == 0 ) { + + conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string; + conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double; + conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long; + conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string; + conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string; + conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double; + conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string; + conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string; + } + + // get the meta data and calculate the size of a row buffer + SQLULEN offset = null_bytes; + for( SQLSMALLINT i = 0; i < col_count; ++i ) { + + core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); + + offset = align_to<4>( offset ); + meta[i].offset = offset; + + switch( meta[i].type ) { + + // these types are the display size + case SQL_BIGINT: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, + reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space + offset += meta[i].length; + break; + + // these types are the column size + case SQL_BINARY: + case SQL_CHAR: + case SQL_SS_UDT: + case SQL_VARBINARY: + case SQL_VARCHAR: + // var* field types are length prefixed + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space + offset += meta[i].length; + } + break; + + case SQL_WCHAR: + case SQL_WVARCHAR: + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + meta[i].length *= sizeof( WCHAR ); + meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space + offset += meta[i].length; + } + break; + + // these types are LOBs + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; + offset += sizeof( void* ); + break; + + // these types are the ISO date size + case SQL_DATETIME: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_TYPE_TIMESTAMP: + core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, + reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space + offset += meta[i].length; + break; + + // these types are the native size + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + meta[i].length = sizeof( long ); + offset += meta[i].length; + break; + + case SQL_REAL: + case SQL_FLOAT: + meta[i].length = sizeof( double ); + offset += meta[i].length; + break; + + default: + SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); + break; + } + + switch( meta[i].type ) { + + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DATETIME: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_LONGVARCHAR: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_XML: + case SQL_TYPE_TIMESTAMP: + case SQL_VARCHAR: + meta[i].c_type = SQL_C_CHAR; + break; + + case SQL_SS_UDT: + case SQL_LONGVARBINARY: + case SQL_BINARY: + case SQL_VARBINARY: + meta[i].c_type = SQL_C_BINARY; + break; + + case SQL_WLONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + meta[i].c_type = SQL_C_WCHAR; + break; + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + meta[i].c_type = SQL_C_LONG; + break; + + case SQL_REAL: + case SQL_FLOAT: + meta[i].c_type = SQL_C_DOUBLE; + break; + + default: + SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); + break; + } + + } + + // read the data into the cache + // (offset from the above loop has the size of the row buffer necessary) + zend_long mem_used = 0; + unsigned long row_count = 0; + + while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { + + // allocate the row buffer + unsigned char* row = static_cast( sqlsrv_malloc( offset )); + memset( row, 0, offset ); + + // read the fields into the row buffer + for( SQLSMALLINT i = 0; i < col_count; ++i ) { + + SQLLEN out_buffer_temp = SQL_NULL_DATA; + SQLPOINTER buffer; + SQLLEN* out_buffer_length = &out_buffer_temp; + + switch( meta[i].c_type ) { + + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + out_buffer_length = &out_buffer_temp; + SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); + *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); + // a NULL pointer means NULL field + if( *lob_addr == NULL ) { + *out_buffer_length = SQL_NULL_DATA; + } + else { + *out_buffer_length = **reinterpret_cast( lob_addr ); + mem_used += *out_buffer_length; + } + } + else { + + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + buffer = row + meta[i].offset + sizeof( SQLULEN ); + out_buffer_length = reinterpret_cast( row + meta[i].offset ); + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); + } + break; + + case SQL_C_LONG: + case SQL_C_DOUBLE: + { + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + buffer = row + meta[i].offset; + out_buffer_length = &out_buffer_temp; + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); + } + break; + + default: + SQLSRV_ASSERT( false, "Unknown C type" ); + break; + } + + if( *out_buffer_length == SQL_NULL_DATA ) { + unsigned char* null_bits = reinterpret_cast( row ); + set_bit( row, i ); + } + } + + SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); + + // add it to the cache + row_dtor_closure cl( this, row ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); + } + +} + +sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) +{ + // free the rows + if( cache ) { + zend_hash_destroy( cache ); + FREE_HASHTABLE( cache ); + cache = NULL; + } + + // free the meta data + if( meta ) { + efree( meta ); + meta = NULL; + } +} + +SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) +{ + last_error = NULL; + last_field_index = -1; + read_so_far = 0; + + switch( orientation ) { + + case SQL_FETCH_NEXT: + offset = 1; + orientation = SQL_FETCH_RELATIVE; + break; + case SQL_FETCH_PRIOR: + offset = -1; + orientation = SQL_FETCH_RELATIVE; + break; + } + + switch( orientation ) { + + case SQL_FETCH_FIRST: + current = 1; + break; + case SQL_FETCH_LAST: + current = row_count( TSRMLS_C ); + break; + case SQL_FETCH_ABSOLUTE: + current = offset; + break; + case SQL_FETCH_RELATIVE: + current += offset; + break; + default: + SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); + break; + } + + // check validity of current row + // the cursor can never get further away than just before the first row + if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { + current = 0; + return SQL_NO_DATA; + } + + // the cursor can never get further away than just after the last row + if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { + current = row_count( TSRMLS_C ) + 1; + return SQL_NO_DATA; + } + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) +{ + last_error = NULL; + field_index--; // convert from 1 based to 0 based + SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); + + if( field_index != last_field_index ) { + last_field_index = field_index; + read_so_far = 0; + } + + unsigned char* row = get_row(); + + // if the field is null, then return SQL_NULL_DATA + if( get_bit( row, field_index )) { + *out_buffer_length = SQL_NULL_DATA; + return SQL_SUCCESS; + } + + // check to make sure the conversion type is valid + if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() || + conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) == + conv_matrix.find( meta[ field_index ].c_type )->second.end() ) { + + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "07006", + (SQLCHAR*) "Restricted data type attribute violation", 0 ); + return SQL_ERROR; + } + + return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, + out_buffer_length ); +} + +SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) +{ + SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); + SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, + "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); + SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, + "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); + + if( last_error == NULL ) { + return SQL_NO_DATA; + } + + SQLSRV_ASSERT( last_error->sqlstate != NULL, + "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); + + memcpy( diag_info_buffer, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); + + return SQL_SUCCESS; +} + +unsigned char* sqlsrv_buffered_result_set::get_row( void ) +{ + row_dtor_closure* cl_ptr; + cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); + SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); + return cl_ptr->row_data; +} + +sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) +{ + // we only hold a single error if there is one, otherwise return the ODBC error(s) + if( last_error == NULL ) { + return odbc_get_diag_rec( odbc, record_number ); + } + if( record_number > 1 ) { + return NULL; + } + + return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); +} + +SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) +{ + last_error = NULL; + + return zend_hash_num_elements( cache ); +} + +// private functions +template +SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out void* buffer, + SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + sqlsrv_error_auto_ptr& out_error ) +{ + // hex characters for the conversion loop below + static char hex_chars[] = "0123456789ABCDEF"; + + SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); + + SQLRETURN r = SQL_ERROR; + + // Set the amount of space necessary for null characters at the end of the data. + SQLSMALLINT extra = sizeof(Char); + + SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " + "multiple of 4 for binary to wide string" ); + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation + *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; + + // copy as much as we can into the buffer + SQLLEN to_copy; + if( buffer_length < *out_buffer_length + extra ) { + to_copy = (buffer_length - extra); + out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + r = SQL_SUCCESS; + to_copy = *out_buffer_length; + } + + // if there are bytes to copy as hex + if( to_copy > 0 ) { + // quick hex conversion routine + Char* h = reinterpret_cast( buffer ); + BYTE* b = reinterpret_cast( field_data ); + // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) + // to get the number of hex digits we can copy + SQLLEN to_copy_hex = to_copy / (2 * extra); + for( int i = 0; i < to_copy_hex; ++i ) { + *h = hex_chars[ (*b & 0xf0) >> 4 ]; + h++; + *h = hex_chars[ (*b++ & 0x0f) ]; + h++; + } + read_so_far += to_copy_hex; + *h = static_cast( 0 ); + } + else { + reinterpret_cast( buffer )[0] = '\0'; + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLCHAR* row = get_row(); + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLCHAR* row = get_row(); + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); +} + + +SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "Buffer length must be able to find a long in " + "sqlsrv_buffered_result_set::double_to_long" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( buffer ); + + if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", + (SQLCHAR*) "Numeric value out of range", 0 ); + return SQL_ERROR; + } + + if( *double_data != floor( *double_data )) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", + (SQLCHAR*) "Fractional truncation", 0 ); + return SQL_SUCCESS_WITH_INFO; + } + + *long_data = static_cast( *double_data ); + *out_buffer_length = sizeof( LONG ); + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( buffer ); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + *double_data = static_cast( *long_data ); + *out_buffer_length = sizeof( double ); + + return SQL_SUCCESS; +} +SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" ); + + unsigned char* row = get_row(); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); + + unsigned char* row = get_row(); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); + SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); + + unsigned char* row = get_row(); + char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); + SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); + + unsigned char* row = get_row(); + SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); + + unsigned char* row = get_row(); + char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); + + unsigned char* row = get_row(); + SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); + SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + SQLCHAR* field_data = NULL; + SQLULEN field_len = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + } + else { + + field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + } + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); + + // to_copy is the number of characters to copy, not including the null terminator + // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. + SQLLEN to_copy; + + if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { + + to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + + r = SQL_SUCCESS; + to_copy = field_len - read_so_far; + } + + if( to_copy > 0 ) { + + bool tried_again = false; + do { + if (to_copy > INT_MAX ) { + LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); + throw core::CoreException(); + } + + int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); + if( ch_space == 0 ) { + + switch( GetLastError() ) { + + case ERROR_NO_UNICODE_TRANSLATION: + // the theory here is the conversion failed because the end of the buffer we provided contained only + // half a character at the end + if( !tried_again ) { + to_copy--; + tried_again = true; + continue; + } + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); + break; + default: + SQLSRV_ASSERT( false, "Severe error translating Unicode" ); + break; + } + + return SQL_ERROR; + } + + ((WCHAR*)buffer)[ to_copy ] = L'\0'; + read_so_far += to_copy; + break; + + } while( true ); + } + else { + reinterpret_cast( buffer )[0] = L'\0'; + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + // Set the amount of space necessary for null characters at the end of the data. + SQLSMALLINT extra = 0; + + switch( meta[ field_index ].c_type ) { + case SQL_C_WCHAR: + extra = sizeof( SQLWCHAR ); + break; + case SQL_C_BINARY: + extra = 0; + break; + case SQL_C_CHAR: + extra = sizeof( SQLCHAR ); + break; + default: + SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); + break; + } + + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; + + // copy as much as we can into the buffer + SQLLEN to_copy; + if( buffer_length < *out_buffer_length + extra ) { + to_copy = buffer_length - extra; + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + r = SQL_SUCCESS; + to_copy = *out_buffer_length; + } + + SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); + + if( to_copy > 0 ) { + memcpy( buffer, field_data + read_so_far, to_copy ); + read_so_far += to_copy; + } + if( extra ) { + OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); + memcpy( reinterpret_cast( buffer ) + to_copy, L"\0", extra ); + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + SQLCHAR* field_data = NULL; + SQLLEN field_len = NULL; + + // if this is the first time called for this field, just convert the entire string to system first then + // use that to read from instead of converting chunk by chunk. This is because it's impossible to know + // the total length of the string for output_buffer_length without doing the conversion and returning + // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and + // to_same_string). + + if( read_so_far == 0 ) { + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + } + else { + + field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + } + + BOOL default_char_used = FALSE; + char default_char = '?'; + + // allocate enough to handle WC -> DBCS conversion if it happens + temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); + temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); + + if( temp_length == 0 ) { + + switch( GetLastError() ) { + + case ERROR_NO_UNICODE_TRANSLATION: + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); + break; + default: + SQLSRV_ASSERT( false, "Severe error translating Unicode" ); + break; + } + + return SQL_ERROR; + } + } + + *out_buffer_length = (temp_length - read_so_far); + + SQLLEN to_copy = 0; + + if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { + + to_copy = buffer_length - sizeof(char); + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + + to_copy = (temp_length - read_so_far); + r = SQL_SUCCESS; + } + + if( to_copy > 0 ) { + + memcpy( buffer, temp_string.get() + read_so_far, to_copy ); + } + SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); + OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); + ((SQLCHAR*) buffer)[ to_copy ] = '\0'; + read_so_far += to_copy; + + return r; +} + + +SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); +} + +SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this + + unsigned char* row = get_row(); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + memcpy( buffer, long_data, sizeof( LONG )); + *out_buffer_length = sizeof( LONG ); + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" ); + SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + memcpy( buffer, double_data, sizeof( double )); + *out_buffer_length = sizeof( double ); + + return SQL_SUCCESS; +} + +namespace { + +// called for each row in the cache when the cache is destroyed in the destructor +void cache_row_dtor( zval* data ) +{ + row_dtor_closure* cl = reinterpret_cast( Z_PTR_P(data) ); + BYTE* row = cl->row_data; + // don't release this here, since this is called from the destructor of the result_set + sqlsrv_buffered_result_set* result_set = cl->results; + + for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { + + if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] ); + sqlsrv_free( out_of_row_data ); + } + } + + sqlsrv_free( row ); +} + +SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, + zend_long mem_used TSRMLS_DC ) +{ + SQLSMALLINT extra = 0; + SQLULEN* output_buffer_len = NULL; + + // Set the amount of space necessary for null characters at the end of the data. + switch( meta.c_type ) { + case SQL_C_WCHAR: + extra = sizeof( SQLWCHAR ); + break; + case SQL_C_BINARY: + extra = 0; + break; + case SQL_C_CHAR: + extra = sizeof( SQLCHAR ); + break; + default: + SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); + break; + } + + SQLLEN already_read = 0; + SQLLEN to_read = INITIAL_FIELD_STRING_LEN; + sqlsrv_malloc_auto_ptr buffer; + buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); + SQLRETURN r = SQL_SUCCESS; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLLEN last_field_len = 0; + bool full_length_returned = false; + + do { + + + output_buffer_len = reinterpret_cast( buffer.get() ); + r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), + to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); + + // if the field is NULL, then return a NULL pointer + if( last_field_len == SQL_NULL_DATA ) { + return NULL; + } + + // if the last read was successful, we're done + if( r == SQL_SUCCESS ) { + // check to make sure we haven't overflown our memory limit + CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + break; + } + // else if it wasn't the truncated warning (01004) then we're done + else if( r == SQL_SUCCESS_WITH_INFO ) { + SQLSMALLINT len; + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + + if( !is_truncated_warning( state )) { + break; + } + } + + SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); + + // if the type of the field returns the total to be read, we use that and preallocate the buffer + if( last_field_len != SQL_NO_TOTAL ) { + + CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + already_read += to_read - already_read; + to_read = last_field_len; + buffer.resize( to_read + extra + sizeof( SQLULEN )); + output_buffer_len = reinterpret_cast( buffer.get() ); + // record the size of the field since we have it available + *output_buffer_len = last_field_len; + full_length_returned = true; + } + // otherwise allocate another chunk of memory to read in + else { + already_read += to_read - already_read; + to_read *= 2; + CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + buffer.resize( to_read + extra + sizeof( SQLULEN )); + output_buffer_len = reinterpret_cast( buffer.get() ); + } + + } while( true ); + + SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); + + // most LOB field types return the total length in the last_field_len, but some field types such as XML + // only return the amount read on the last read + if( !full_length_returned ) { + *output_buffer_len = already_read + last_field_len; + } + + char* return_buffer = buffer; + buffer.transferred(); + return return_buffer; +} + +} diff --git a/pdo_sqlsrv/core_sqlsrv.h b/pdo_sqlsrv/core_sqlsrv.h new file mode 100644 index 00000000..6858b1c9 --- /dev/null +++ b/pdo_sqlsrv/core_sqlsrv.h @@ -0,0 +1,2249 @@ +#ifndef CORE_SQLSRV_H +#define CORE_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_sqlsrv.h +// +// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +//********************************************************************************************************************************* +// Includes +//********************************************************************************************************************************* + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef PHP_WIN32 +#define PHP_SQLSRV_API __declspec(dllexport) +#else +#define PHP_SQLSRV_API +#endif + +// OACR is an internal Microsoft static code analysis tool +#if defined(OACR) +#include +OACR_WARNING_PUSH +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) +OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) +#else +// define to eliminate static analysis hints in the code +#define OACR_WARNING_SUPPRESS( warning, msg ) +#endif + +extern "C" { + +#pragma warning(push) +#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if _MSC_VER >= 1400 +// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. +// php.h defines this constant as unsigned int which causes a compile error +// in ws2tcpip.h. Fortunately php.h allows an override by defining +// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define +// socklen_t here and override the php.h version. +typedef int socklen_t; +#define HAVE_SOCKLEN_T +#endif + +#include "php.h" +#include "php_globals.h" +#include "php_ini.h" +#include "ext/standard/php_standard.h" +#include "ext/standard/info.h" + +#pragma warning(pop) + +#if ZEND_DEBUG +// debug build causes warning C4505 to pop up from the Zend header files +#pragma warning( disable: 4505 ) +#endif + +} // extern "C" + +#if defined(OACR) +OACR_WARNING_POP +#endif + +#include +#include + +#if !defined(WC_ERR_INVALID_CHARS) +// imported from winnls.h as it isn't included by 5.3.0 +#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars +#endif + +// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when +// we use std::copy, which causes compilation to fail since we compile with warnings as errors. +#if defined(ZEND_DEBUG) && defined(inline) +#undef inline +#endif + +#include +#include +#include +#include +#include +#include +#include +// included for SQL Server specific constants +#include "msodbcsql.h" + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// constants for maximums in SQL Server +const int SS_MAXCOLNAMELEN = 128; +const int SQL_SERVER_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_MAX_PRECISION = 38; +const int SQL_SERVER_MAX_TYPE_SIZE = 0; +const int SQL_SERVER_MAX_PARAMS = 2100; + +// max size of a date time string when converting from a DateTime object to a string +const int MAX_DATETIME_STRING_LEN = 256; + +// precision and scale for the date time types between servers +const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; +const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; +const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; +const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; + +// 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_DEFAULT, // use what is the connection's default for a statement, use system if a connection + SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData + SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData + SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, + SQLSRV_ENCODING_UTF8 = CP_UTF8, +}; + +// 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; + +// buffer size allocated to retrieve data from a PHP stream. This number +// was chosen since PHP doesn't return more than 8k at a time even if +// the amount requested was more. +const int PHP_STREAM_BUFFER_SIZE = 8192; + +// 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; + + zend_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; + unsigned encoding:16; + } typeinfo; + + zend_long value; +}; + +// static assert for enforcing compile time conditions +template +struct sqlsrv_static_assert; + +template <> +struct sqlsrv_static_assert { static const int value = 1; }; + +#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) + + +//********************************************************************************************************************************* +// Logging +//********************************************************************************************************************************* +// log_callback +// a driver specific callback for logging messages +// severity - severity of the message: notice, warning, or error +// msg - the message to log in a FormatMessage style formatting +// print_args - args to the message +typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +// each driver must register a log callback. This should be the first thing a driver does. +void core_sqlsrv_register_logger( log_callback ); + +// a simple wrapper around a PHP error logging function. +void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); + +// a macro to make it convenient to use the function. +#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ ) + +// 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, +}; + +// Kill the PHP process and log the message to PHP +void die( const char* msg, ... ); +#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); } + + +//********************************************************************************************************************************* +// Resource/Memory Management +//********************************************************************************************************************************* + +// the macro max is defined and overrides the call to max in the allocator class +#pragma push_macro( "max" ) +#undef max + +// new memory allocation/free debugging facilities to help us verify that all allocations are being +// released in a timely manner and not just at the end of the script. +// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. +// It's meant for internal use but might be useful for people adding features to our extension. +// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases +// must be done with sqlsrv_malloc and sqlsrv_free. +// #define SQLSRV_MEM_DEBUG 1 +#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) + +inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) +{ + void* ptr = emalloc( size ); + LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); + return ptr; +} + +inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) +{ + OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); + + if(( element_count > 0 && element_size > 0 ) && + ( element_count > element_size * element_count || element_size > element_size * element_count )) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count > element_size * element_count + extra ) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count + extra == 0 ) { + DIE( "Allocation size must be more than 0" ); + } + + void* ptr = emalloc( element_size * element_count + extra ); + LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); + return ptr; +} + +inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) +{ + void* ptr = erealloc( original, size ); + LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); + return ptr; +} + +inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) +{ + LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); + efree( ptr ); +} + +#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) +#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) +#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) +#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) + +#else + +inline void* sqlsrv_malloc( size_t size ) +{ + return emalloc( size ); +} + +inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) +{ + OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); + + if(( element_count > 0 && element_size > 0 ) && + ( element_count > element_size * element_count || element_size > element_size * element_count )) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count > element_size * element_count + extra ) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count + extra == 0 ) { + DIE( "Allocation size must be more than 0" ); + } + + return emalloc( element_size * element_count + extra ); +} + +inline void* sqlsrv_realloc( void* buffer, size_t size ) +{ + return erealloc( buffer, size ); +} + +inline void sqlsrv_free( void* ptr ) +{ + efree( ptr ); +} + +#endif + +// trait class that allows us to assign const types to an auto_ptr +template +struct remove_const { + typedef T type; +}; + +template +struct remove_const { + typedef T* type; +}; + +// allocator that uses the zend memory manager to manage memory +// this allows us to use STL classes that still work with Zend objects +template +struct sqlsrv_allocator { + + // typedefs used by the STL classes + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // conversion typedef (used by list and other STL classes) + template + struct rebind { + typedef sqlsrv_allocator other; + }; + + inline sqlsrv_allocator() {} + inline ~sqlsrv_allocator() {} + inline sqlsrv_allocator( sqlsrv_allocator const& ) {} + template + inline sqlsrv_allocator( sqlsrv_allocator const& ) {} + + // address (doesn't work if the class defines operator&) + inline pointer address( reference r ) + { + return &r; + } + + inline const_pointer address( const_reference r ) + { + return &r; + } + + // memory allocation/deallocation + inline pointer allocate( size_type cnt, + typename std::allocator::const_pointer = 0 ) + { + return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); + } + + inline void deallocate( pointer p, size_type ) + { + sqlsrv_free(p); + } + + // size + inline size_type max_size( void ) const + { + return std::numeric_limits::max() / sizeof(T); + } + + // object construction/destruction + inline void construct( pointer p, const T& t ) + { + new(p) T(t); + } + + inline void destroy(pointer p) + { + p->~T(); + } + + // equality operators + inline bool operator==( sqlsrv_allocator const& ) + { + return true; + } + + inline bool operator!=( sqlsrv_allocator const& a ) + { + return !operator==(a); + } +}; + + +// base class for auto_ptrs that we define below. It provides common operators and functions +// used by all the classes. +template +class sqlsrv_auto_ptr { + +public: + + sqlsrv_auto_ptr( void ) : _ptr( NULL ) + { + } + + ~sqlsrv_auto_ptr( void ) + { + static_cast(this)->reset( NULL ); + } + + // call when ownership is transferred + void transferred( void ) + { + _ptr = NULL; + } + + // explicit function to get the pointer. + T* get( void ) const + { + return _ptr; + } + + // cast operator to allow auto_ptr to be used where a normal const * can be. + operator const T* () const + { + return _ptr; + } + + // cast operator to allow auto_ptr to be used where a normal pointer can be. + operator typename remove_const::type () const + { + return _ptr; + } + + operator bool() const + { + return _ptr != NULL; + } + + // 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[]( long index ) const + { + return _ptr[ index ]; + } + + + #ifdef __WIN64 + // 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[](std::size_t index) const + { + return _ptr[index]; + } + #endif + + // 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; + } + + // value from reference operator (i.e., i = *(&i); or *i = blah;) + T& operator*() const + { + return *_ptr; + } + + // allow the use of the address-of operator to simulate a **. + // Note: this operator conflicts with storing these within an STL container. If you need + // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() + T** operator&( void ) + { + return &_ptr; + } + +protected: + + sqlsrv_auto_ptr( T* ptr ) : + _ptr( ptr ) + { + } + + sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) + { + if( _ptr ) { + static_cast(this)->reset( src._ptr ); + } + src.transferred(); + } + + // assign a new pointer to the auto_ptr. It will free the previous memory block + // because ownership is deemed finished. + T* operator=( T* ptr ) + { + static_cast( this )->reset( ptr ); + + return ptr; + } + + T* _ptr; +}; + +// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer +// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_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". +// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. + +template +class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { + +public: + + sqlsrv_malloc_auto_ptr( void ) : + sqlsrv_auto_ptr >( NULL ) + { + } + + sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : + 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 ) + sqlsrv_free( (void*) _ptr ); + _ptr = ptr; + } + + T* operator=( T* ptr ) + { + return sqlsrv_auto_ptr >::operator=( ptr ); + } + + void operator=( sqlsrv_malloc_auto_ptr& src ) + { + T* p = src.get(); + src.transferred(); + this->_ptr = p; + } + + // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. + // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer + void resize( size_t new_size ) + { + _ptr = reinterpret_cast( sqlsrv_realloc( _ptr, new_size )); + } +}; + + +// auto ptr for Zend hash tables. Used to clean up a hash table allocated when +// something caused an early exit from the function. This is used when the hash_table is +// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. + +class hash_auto_ptr : public sqlsrv_auto_ptr { + +public: + + hash_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( HashTable* ptr = NULL ) + { + if( _ptr ) { + zend_hash_destroy( _ptr ); + FREE_HASHTABLE( _ptr ); + } + _ptr = ptr; + } + + HashTable* operator=( HashTable* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + +private: + + hash_auto_ptr( HashTable const& hash ); + + hash_auto_ptr( hash_auto_ptr const& hash ); +}; + + +// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. +// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed +// (out of scope) or ownership is transferred using the function "transferred". + +class zval_auto_ptr : public sqlsrv_auto_ptr { + +public: + + zval_auto_ptr( void ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( zval* ptr = NULL ) + { + if( _ptr ) + zval_ptr_dtor(_ptr ); + _ptr = ptr; + } + + zval* operator=( zval* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + + +private: + + zval_auto_ptr( const zval_auto_ptr& src ); +}; + +#pragma pop_macro( "max" ) + + +//********************************************************************************************************************************* +// sqlsrv_error +//********************************************************************************************************************************* + +// *** PHP specific errors *** +// sqlsrv errors are held in a structure of this type used by the driver handle_error functions +// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage +// into the error message before returning it. + +// base class which can be instatiated with aggregates (see error constants) +struct sqlsrv_error_const { + + SQLCHAR* sqlstate; + SQLCHAR* native_message; + SQLINTEGER native_code; + bool format; +}; + +// subclass which is used by the core layer to instantiate ODBC errors +struct sqlsrv_error : public sqlsrv_error_const { + + sqlsrv_error( void ) + { + sqlstate = NULL; + native_message = NULL; + native_code = -1; + format = false; + } + + sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) + { + sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); + native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); + strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); + native_code = code; + format = printf_format; + } + + sqlsrv_error( sqlsrv_error_const const& prototype ) + { + sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); + } + + ~sqlsrv_error( void ) + { + if( sqlstate != NULL ) { + sqlsrv_free( sqlstate ); + } + if( native_message != NULL ) { + sqlsrv_free( native_message ); + } + } +}; + + +// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete +class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { + +public: + + sqlsrv_error_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : + sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( sqlsrv_error* ptr = NULL ) + { + if( _ptr ) { + _ptr->~sqlsrv_error(); + sqlsrv_free( (void*) _ptr ); + } + _ptr = ptr; + } + + sqlsrv_error* operator=( sqlsrv_error* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + + // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much + // sense. Only the last one would have anything in it. + void operator=( sqlsrv_error_auto_ptr& src ) + { + sqlsrv_error* p = src.get(); + src.transferred(); + this->_ptr = p; + } +}; + + +//********************************************************************************************************************************* +// Context +//********************************************************************************************************************************* + +class sqlsrv_context; +struct sqlsrv_conn; + +// error_callback +// a driver specific callback for processing errors. +// ctx - the context holding the handles +// sqlsrv_error_code - specific error code to return. +typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); + +// sqlsrv_context +// a context holds relevant information to be passed with a connection and statement objects. + +class sqlsrv_context { + + public: + + sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : + handle_( SQL_NULL_HANDLE ), + handle_type_( type ), + err_( e ), + name_( NULL ), + driver_( drv ), + last_error_(), + encoding_( encoding ) + { + } + + sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : + handle_( h ), + handle_type_( t ), + err_( e ), + name_( NULL ), + driver_( drv ), + last_error_(), + encoding_( encoding ) + { + } + + sqlsrv_context( sqlsrv_context const& ctx ) : + handle_( ctx.handle_ ), + handle_type_( ctx.handle_type_ ), + err_( ctx.err_ ), + name_( ctx.name_ ), + driver_( ctx.driver_ ), + last_error_( ctx.last_error_ ) + { + } + + void set_func( const char* f ) + { + name_ = f; + } + + void set_last_error( sqlsrv_error_auto_ptr& last_error ) + { + last_error_ = last_error; + } + + sqlsrv_error_auto_ptr& last_error( void ) + { + return last_error_; + } + + // since the primary responsibility of a context is to hold an ODBC handle, we + // provide these convenience operators for using them interchangeably + operator SQLHANDLE ( void ) const + { + return handle_; + } + + error_callback error_handler( void ) const + { + return err_; + } + + SQLHANDLE handle( void ) const + { + return handle_; + } + + SQLSMALLINT handle_type( void ) const + { + return handle_type_; + } + + const char* func( void ) const + { + return name_; + } + + void* driver( void ) const + { + return driver_; + } + + void set_driver( void* driver ) + { + this->driver_ = driver; + } + + void invalidate( void ) + { + if( handle_ != SQL_NULL_HANDLE ) { + ::SQLFreeHandle( handle_type_, handle_ ); + } + handle_ = SQL_NULL_HANDLE; + } + + bool valid( void ) + { + return handle_ != SQL_NULL_HANDLE; + } + + SQLSRV_ENCODING encoding( void ) const + { + return encoding_; + } + + void set_encoding( SQLSRV_ENCODING e ) + { + encoding_ = e; + } + + private: + SQLHANDLE handle_; // ODBC handle for this context + SQLSMALLINT handle_type_; // type of the ODBC handle + const char* name_; // function name currently executing this context + error_callback err_; // driver error callback if error occurs in core layer + void* driver_; // points back to the driver for PDO + sqlsrv_error_auto_ptr last_error_; // last error that happened on this object + SQLSRV_ENCODING encoding_; // encoding of the context +}; + +const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista + +// maps an IANA encoding to a code page +struct sqlsrv_encoding { + + const char* iana; + size_t iana_len; + unsigned int code_page; + bool not_for_connection; + + sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): + iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) + { + } +}; + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// variables set during initialization +extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in +extern HashTable* g_encodings; // encodings supported by this driver + +void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); +void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); + +// environment context used by sqlsrv_connect for when a connection error occurs. +struct sqlsrv_henv { + + sqlsrv_context ctx; + + sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : + ctx( handle, SQL_HANDLE_ENV, e, drv ) + { + } +}; + + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* + +// supported server versions (determined at connection time) +enum SERVER_VERSION { + SERVER_VERSION_UNKNOWN = -1, + SERVER_VERSION_2000 = 8, + SERVER_VERSION_2005, + SERVER_VERSION_2008, // use this for anything 2008 or later +}; + +// forward decl +struct sqlsrv_stmt; +struct stmt_option; + +// *** connection resource structure *** +// this is the resource structure returned when a connection is made. +struct sqlsrv_conn : public sqlsrv_context { + + // instance variables + SERVER_VERSION server_version; // version of the server that we're connected to + + // initialize with default values + sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : + sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) + { + } + + // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be + // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating + // the sqlsrv_conn with a connection. +}; + +enum SQLSRV_STMT_OPTIONS { + + SQLSRV_STMT_OPTION_INVALID, + SQLSRV_STMT_OPTION_QUERY_TIMEOUT, + SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, + SQLSRV_STMT_OPTION_SCROLLABLE, + SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + + // Driver specific connection options + SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, + +}; + +namespace ODBCConnOptions { + +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char CharacterSet[] = "CharacterSet"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_ODBC[] = "MARS_Connection"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char WSID[] = "WSID"; +const char UID[] = "UID"; +const char PWD[] = "PWD"; +const char SERVER[] = "Server"; + +} + +enum SQLSRV_CONN_OPTIONS { + + SQLSRV_CONN_OPTION_INVALID, + SQLSRV_CONN_OPTION_APP, + SQLSRV_CONN_OPTION_CHARACTERSET, + SQLSRV_CONN_OPTION_CONN_POOLING, + SQLSRV_CONN_OPTION_DATABASE, + SQLSRV_CONN_OPTION_ENCRYPT, + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + SQLSRV_CONN_OPTION_MARS, + SQLSRV_CONN_OPTION_QUOTED_ID, + SQLSRV_CONN_OPTION_TRACE_FILE, + SQLSRV_CONN_OPTION_TRACE_ON, + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + SQLSRV_CONN_OPTION_WSID, + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + + // Driver specific connection options + SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, + +}; + + +#define NO_ATTRIBUTE -1 + +// type of connection attributes +enum CONN_ATTR_TYPE { + CONN_ATTR_INT, + CONN_ATTR_BOOL, + CONN_ATTR_STRING, + CONN_ATTR_INVALID, +}; + +// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or +// sets an attribute) +struct connection_option { + // the name of the option as passed in by the user + const char * sqlsrv_name; + unsigned int sqlsrv_len; + + unsigned int conn_option_key; + // the name of the option in the ODBC connection string + const char * odbc_name; + unsigned int odbc_len; + enum CONN_ATTR_TYPE value_type; + + // process the connection type + // return whether or not the function was successful in processing the connection option + void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); +}; + +// connection attribute functions +template +struct str_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), + static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +// simply add the parsed value to the connection string +struct conn_str_append_func { + + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); +}; + +struct conn_null_func { + + static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ + TSRMLS_DC ); +}; + +// factory to create a connection (since they are subclassed to instantiate statements) +typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); + +// *** connection functions *** +sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, + const char* server, const char* uid, const char* pwd, + HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], + void* driver, const char* driver_func TSRMLS_DC ); +void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); +void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval* server_info TSRMLS_DC ); +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ); +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ); +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); +size_t core_str_zval_is_true( zval* str_zval ); + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); +}; + +struct stmt_option_query_timeout : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_send_at_exec : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_buffered_query_limit : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +// used to hold the table for statment options +struct stmt_option { + + const char * name; // name of the statement option + unsigned int name_len; // name length + unsigned int key; + std::unique_ptr func; // callback that actually handles the work of the option + +}; + +// holds the stream param and the encoding that it was assigned +struct sqlsrv_stream { + + zval* stream_z; + SQLSRV_ENCODING encoding; + SQLUSMALLINT field_index; + SQLSMALLINT sql_type; + sqlsrv_stmt* stmt; + std::size_t stmt_index; + + sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : + stream_z( str_z ), encoding( enc ) + { + } + + sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL ) + { + } +}; + +// close any active stream +void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ); + +extern php_stream_wrapper g_sqlsrv_stream_wrapper; + +// resource constants used when registering the stream type with PHP +#define SQLSRV_STREAM_WRAPPER "sqlsrv" +#define SQLSRV_STREAM "sqlsrv_stream" + +// holds the output parameter information. Strings also need the encoding and other information for +// after processing. Only integer, float, and strings are allowable output parameters. +struct sqlsrv_output_param { + + zval* param_z; + SQLSRV_ENCODING encoding; + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement + SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer + bool is_bool; + + // string output param constructor + sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : + param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) + { + } + + // every other type output parameter constructor + sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : + param_z( p_z ), + param_num( num ), + encoding( SQLSRV_ENCODING_INVALID ), + original_buffer_len( -1 ), + is_bool( is_bool ) + { + } +}; + +// forward decls +struct sqlsrv_result_set; + +// *** Statement resource structure *** +struct sqlsrv_stmt : public sqlsrv_context { + + void free_param_data( TSRMLS_D ); + virtual void new_result_set( TSRMLS_D ); + + sqlsrv_conn* conn; // Connection that created this statement + + bool executed; // Whether the statement has been executed yet (used for error messages) + bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row + sqlsrv_result_set* current_results; // Current result set + SQLULEN cursor_type; // Type of cursor for the current result set + bool has_rows; // Has_rows is set if there are actual rows in the row set + bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called + int last_field_index; // last field retrieved by core_sqlsrv_get_field + bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the + // last results + unsigned long query_timeout; // maximum allowed statement execution time + zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) + + // holds output pointers for SQLBindParameter + // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving + // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold + std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter + zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP + zval output_params; // hold all the output parameters + zval param_streams; // track which streams to send data to the server + zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects + bool send_streams_at_exec; // send all stream data right after execution before returning + sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter + unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string + // to the server) + zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals + zval* active_stream; // the currently active stream reading data from the database + + sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); + virtual ~sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + +}; + +// *** field metadata struct *** +struct field_meta_data { + + sqlsrv_malloc_auto_ptr field_name; + SQLSMALLINT field_name_len; + SQLSMALLINT field_type; + SQLULEN field_size; + SQLULEN field_precision; + SQLSMALLINT field_scale; + SQLSMALLINT field_is_nullable; + + field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), + field_scale (0), field_is_nullable(0) + { + } + + ~field_meta_data() + { + } +}; + +// *** statement constants *** +// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value +const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; +const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) + +// uninitialized query timeout value +const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; + +// special buffered query constant +const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant + +// factory to create a statement +typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); + +// *** statement functions *** +sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, + const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, + SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, + SQLSMALLINT decimal_digits TSRMLS_DC ); +void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); +field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); +void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, + __out void** field_value, __out SQLLEN* field_length, bool cache_field, + __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ); +bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); +void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, SQLULEN cursor_type TSRMLS_DC ); +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); + + +//********************************************************************************************************************************* +// Result Set +//********************************************************************************************************************************* + +// Abstract the result set so that a result set can either be used as is from ODBC or buffered. +// This is not a complete abstraction of a result set. Only enough is abstracted to allow for +// information and capabilities normally not available when a result set is not buffered +// (e.g., forward only vs buffered means row count is available and cursor movement is possible). +// Otherwise, normal ODBC calls are still valid and should be used to get information about the +// result set (e.g., SQLNumResultCols). + +struct sqlsrv_result_set { + + sqlsrv_stmt* odbc; + + explicit sqlsrv_result_set( sqlsrv_stmt* ); + virtual ~sqlsrv_result_set( void ) { } + + virtual bool cached( int field_index ) = 0; + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC )= 0; + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; + virtual SQLLEN row_count( TSRMLS_D ) = 0; +}; + +struct sqlsrv_odbc_result_set : public sqlsrv_result_set { + + explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); + virtual ~sqlsrv_odbc_result_set( void ); + + virtual bool cached( int field_index ) { return false; } + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ); + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length TSRMLS_DC ); + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); + virtual SQLLEN row_count( TSRMLS_D ); + + private: + // prevent invalid instantiations and assignments + sqlsrv_odbc_result_set( void ); + sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); + sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); +}; + +struct sqlsrv_buffered_result_set : public sqlsrv_result_set { + + struct meta_data { + SQLSMALLINT type; + SQLSMALLINT c_type; // convenience + SQLULEN offset; // in bytes + SQLULEN length; // in bytes + SQLSMALLINT scale; + + static const SQLULEN SIZE_UNKNOWN = 0; + }; + + // default maximum amount of memory that a buffered query can consume + #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings + static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB + static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; + + explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); + virtual ~sqlsrv_buffered_result_set( void ); + + virtual bool cached( int field_index ) { return true; } + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ); + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length TSRMLS_DC ); + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); + virtual SQLLEN row_count( TSRMLS_D ); + + // buffered result set specific + SQLSMALLINT column_count( void ) + { + return col_count; + } + + struct meta_data& col_meta_data( SQLSMALLINT i ) + { + return meta[i]; + } + + private: + // prevent invalid instantiations and assignments + sqlsrv_buffered_result_set( void ); + sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); + sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); + + HashTable* cache; // rows of data kept in index based hash table + SQLSMALLINT col_count; // number of columns in the current result set + meta_data* meta; // metadata for fields in the cache + SQLLEN current; // 1 based, 0 means before first row + sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here + SQLUSMALLINT last_field_index; // the last field data retrieved from + SQLLEN read_so_far; // position within string to read from (for partial reads of strings) + sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use + SQLLEN temp_length; // number of bytes in the temp conversion buffer + + typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; + + // two dimentional sparse matrix that holds the [from][to] functions that do conversions + static conv_matrix_t conv_matrix; + + // string conversion functions + SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + + // long conversion functions + SQLRETURN to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length ); + SQLRETURN long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + + // double conversion functions + SQLRETURN to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length ); + SQLRETURN double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + + // string to number conversion functions + // Future: See if these can be converted directly to template member functions + SQLRETURN string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, + __out SQLLEN* out_buffer_length ); + + // utility functions for conversions + unsigned char* get_row( void ); +}; + +//********************************************************************************************************************************* +// Utility +//********************************************************************************************************************************* + +// 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; + +// do a heap check in debug mode, but only print errors, not all of the allocations +#define MEMCHECK_SILENT 1 + +// utility functions shared by multiple callers across files +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); +bool validate_string(char* string, SQLLEN& len); +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); +wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, + unsigned int mbcs_len, __out unsigned int* utf16_len ); + +//********************************************************************************************************************************* +// Error handling routines and Predefined Errors +//********************************************************************************************************************************* + +enum SQLSRV_ERROR_CODES { + + SQLSRV_ERROR_ODBC, + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + SQLSRV_ERROR_ZEND_HASH, + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + SQLSRV_ERROR_ZEND_STREAM, + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + SQLSRV_ERROR_FETCH_PAST_END, + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + SQLSRV_ERROR_NO_FIELDS, + SQLSRV_ERROR_INVALID_TYPE, + SQLSRV_ERROR_FETCH_NOT_CALLED, + SQLSRV_ERROR_NO_DATA, + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + SQLSRV_ERROR_INVALID_CONNECTION_KEY, + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + SQLSRV_ERROR_INVALID_OPTION_KEY, + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + SQLSRV_ERROR_STREAM_CREATE, + SQLSRV_ERROR_MARS_OFF, + SQLSRV_ERROR_FIELD_INDEX_ERROR, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + + // Driver specific error codes starts from here. + SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, + +}; + +// the message returned by ODBC Driver 11 for SQL Server +const char CONNECTION_BUSY_ODBC_ERROR[] = "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for " + "another command"; + +// SQLSTATE for all internal errors +extern SQLCHAR IMSSP[]; + +// SQLSTATE for all internal warnings +extern SQLCHAR SSPWARN[]; + +// flags passed to sqlsrv_errors to filter its return values +enum error_handling_flags { + SQLSRV_ERR_ERRORS, + SQLSRV_ERR_WARNINGS, + SQLSRV_ERR_ALL +}; + +// *** internal error macros and functions *** +// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the +// errno is 1 based. It returns it as an array with 3 members: +// 1/SQLSTATE) sqlstate +// 2/code) driver specific error code +// 3/message) driver specific error message +// The fetch type determines if the indices are numeric, associative, or both. +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, __out sqlsrv_error_auto_ptr& error, + logging_severity severity TSRMLS_DC ); + +// format and return a driver specfic error +void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, + sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); + + +// return the message for the HRESULT returned by GetLastError. Some driver errors use this to +// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. +const char* get_last_error_message( DWORD last_error = 0 ); + +// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer +DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); + +// convenience functions that overload either a reference or a pointer so we can use +// either in the CHECK_* functions. +inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +{ + va_list print_params; + va_start( print_params, warning ); + bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + va_end( print_params ); + return ignored; +} + +inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +{ + va_list print_params; + va_start( print_params, warning ); + bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + va_end( print_params ); + return ignored; +} + +// 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 SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, __VA_ARGS__ ); + +#if defined( PHP_DEBUG ) + +#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ + if( !(condition)) { \ + DIE (msg, __VA_ARGS__ ); \ + } + +#else + + #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) + +#endif + +// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. +inline bool is_truncated_warning( SQLCHAR* state ) +{ +#if defined(ZEND_DEBUG) + if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ + DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ + } +#endif + return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); +} + +// Macros for handling errors. These macros are simplified if statements that take boilerplate +// code down to a single line to avoid distractions in the code. + +#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ + bool flag##unique = (condition); \ + bool ignored##unique = true; \ + if (flag##unique) { \ + ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, __VA_ARGS__ ); \ + } \ + if( !ignored##unique ) + +#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ + CHECK_ERROR_EX( unique, condition, context, ssphp, __VA_ARGS__ ) + +#define CHECK_ERROR( condition, context, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, NULL, __VA_ARGS__ ) + +#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) + +#define CHECK_SQL_ERROR( result, context, ... ) \ + SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ + CHECK_ERROR( result == SQL_ERROR, context, __VA_ARGS__ ) + +#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ + bool ignored##unique = true; \ + if( condition ) { \ + ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, __VA_ARGS__ ); \ + } \ + if( !ignored##unique ) + +#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ + CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, __VA_ARGS__ ) + +#define CHECK_SQL_WARNING( result, context, ... ) \ + if( result == SQL_SUCCESS_WITH_INFO ) { \ + (void)call_error_handler( context, NULL TSRMLS_CC, /*warning*/ true, __VA_ARGS__ ); \ + } + +#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ + CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) + +#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, __VA_ARGS__ ) \ + +#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ + SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ + bool ignored = true; \ + if( result == SQL_ERROR ) { \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, __VA_ARGS__ ); \ + } \ + else if( result == SQL_SUCCESS_WITH_INFO ) { \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, __VA_ARGS__ ); \ + } \ + if( !ignored ) + +// throw an exception after it has been hooked into the custom error handler +#define THROW_CORE_ERROR( ctx, custom, ... ) \ + (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, __VA_ARGS__ ); \ + throw core::CoreException(); + +//********************************************************************************************************************************* +// ODBC/Zend function wrappers +//********************************************************************************************************************************* + +namespace core { + + // base exception for the driver + struct CoreException : public std::exception { + + CoreException() + { + } + }; + + inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) + { + // We check for the 'connection busy' error caused by having MultipleActiveResultSets off + // and return a more helpful message prepended to the ODBC errors if that error occurs + if( !SQL_SUCCEEDED( r )) { + + SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ]; + SQLSMALLINT len = 0; + + SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, + err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + + throw CoreException(); + } + + if(( len == sizeof( CONNECTION_BUSY_ODBC_ERROR ) - 1 ) && + !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR )) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); + } + } + } + + // *** ODBC wrappers *** + + // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors + // some of the signatures have been altered to be more convenient since the return value is no longer + // required to return the status of the call (e.g., SQLNumResultCols). + // These functions take the sqlsrv_context type. However, since the error handling code can alter + // the context to hold the error, they are not passed as const. + + inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + { + SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, + diag_info_buffer, buffer_length, out_buffer_length ); + + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + + return r; + } + + inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, + __out_ecount(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); + CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { + throw CoreException(); + } + } + + inline void SQLBindParameter( sqlsrv_stmt* stmt, + SQLUSMALLINT ParameterNumber, + SQLSMALLINT InputOutputType, + SQLSMALLINT ValueType, + SQLSMALLINT ParameterType, + SQLULEN ColumnSize, + SQLSMALLINT DecimalDigits, + __inout SQLPOINTER ParameterValuePtr, + SQLLEN BufferLength, + __inout SQLLEN * StrLen_Or_IndPtr + TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, + DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, + __out SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + __out SQLSMALLINT* out_buffer_length, __out SQLLEN* field_type_num TSRMLS_DC ) + { + SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, + buffer_length, out_buffer_length, field_type_num ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, __out_z SQLCHAR* col_name, SQLSMALLINT col_name_length, + __out SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, __out SQLULEN* col_size, + __out SQLSMALLINT* decimal_digits, __out SQLSMALLINT* nullable TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, + data_type, col_size, decimal_digits, nullable); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) + { + SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) + { + SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + + throw CoreException(); + } + return r; + } + + inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLExecute( stmt->handle() ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return r; + } + + inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) + { + SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + + // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine + inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} + } + + inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, + __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) + { + SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); + + if( r == SQL_NO_DATA ) + return r; + + CHECK_SQL_ERROR( r, stmt ) { + throw CoreException(); + } + + if( handle_warning ) { + CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { + throw CoreException(); + } + } + + return r; + } + + + inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, __out SQLPOINTER info_value, SQLSMALLINT buffer_len, + __out SQLSMALLINT* str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + + inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLGetTypeInfo( stmt->handle(), data_type ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. + inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r = ::SQLMoreResults( stmt->handle() ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return r; + } + + inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + SQLSMALLINT num_cols; + r = ::SQLNumResultCols( stmt->handle(), &num_cols ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return num_cols; + } + + // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more + // parameters or when the parameters are all processed. + inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, __out SQLPOINTER* value_ptr_ptr TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + SQLLEN rows_affected; + + r = ::SQLRowCount( stmt->handle(), &rows_affected ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return rows_affected; + } + + + inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + } + + + inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + } + + inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) + { + SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + // *** zend wrappers *** + + // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string + // so allocated memory inside of value_z should be released before assigning it to the new string + inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) + { + if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { + zend_string* temp_zstr = zend_string_init(str, str_len, 0); + zend_string_release(Z_STR_P(value_z)); + ZVAL_NEW_STR(value_z, temp_zstr); + } + else { + ZVAL_STRINGL(value_z, str, str_len); + } + } + + + // exception thrown when a zend function wrapped here fails. + + // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw + // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are + // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. + // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error + // that can be thrown from it. + + inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) + { + int zr = ::add_index_zval( array, index, value ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) + { + int zr = ::add_next_index_zval( array, value ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) + { + int zr = ::add_assoc_null( array_z, key ); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) + { + int zr = ::add_assoc_long( array_z, key, val ); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) + { + int zr = ::add_assoc_string(array_z, key, val); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + if (duplicate == 0) { + sqlsrv_free(val); + } + } + + inline void sqlsrv_array_init( sqlsrv_context& ctx, __out zval* new_array TSRMLS_DC) + { + int zr = ::array_init(new_array); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) + { + // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment + php_stream_from_zval_no_verify( stream, stream_z ); + CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, __out zval*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, __out void*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) + { + int zr = ::zend_hash_index_del( ht, index ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) + { + int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + + inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) + { + int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, + dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) + { + ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); + } + + inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, + unsigned int data_size, zval* pDest TSRMLS_DC ) + { + int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + +template +sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) +{ + return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); +} + +template +sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) +{ + return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); +} + +} // namespace core + +#endif // CORE_SQLSRV_H diff --git a/pdo_sqlsrv/core_stmt.cpp b/pdo_sqlsrv/core_stmt.cpp new file mode 100644 index 00000000..16f0a950 --- /dev/null +++ b/pdo_sqlsrv/core_stmt.cpp @@ -0,0 +1,2441 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_stmt.cpp +// +// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the +// results of every field request, and if it is out of order, we cache those for preceding fields. +struct field_cache { + + void* value; + SQLLEN len; + sqlsrv_phptype type; + + field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) + : type( t ) + { + // if the value is NULL, then just record a NULL pointer + if( field_value != NULL ) { + value = sqlsrv_malloc( field_len ); + memcpy( value, field_value, field_len ); + len = field_len; + } + else { + value = NULL; + len = 0; + } + } + + // no destructor because we don't want to release the memory when it goes out of scope, but instead we + // rely on the hash table destructor to free the memory +}; + +const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field + +// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads +const unsigned int UTF8_MIDBYTE_MASK = 0xc0; +const unsigned int UTF8_MIDBYTE_TAG = 0x80; +const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; +const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; +const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; +const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; +const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; + +// 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 +namespace DateTime { + +const char DATETIME_CLASS_NAME[] = "DateTime"; +const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; +const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; +const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); +const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; +const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); +const char DATE_FORMAT[] = "Y-m-d"; +const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); + +} + +// *** internal functions *** +// Only declarations are put here. Functions contain the documentation they need at their definition sites. +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC ); +size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); +bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); +bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); +void core_get_field_common(__inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ); +// returns the ODBC C type constant that matches the PHP type and encoding given +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); +void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, + __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ); +// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + __out SQLSMALLINT& sql_type TSRMLS_DC ); +void field_cache_dtor( zval* data_z ); +void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ); +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); +// assure there is enough space for the output parameter string +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, + SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, + SQLLEN& buffer_len TSRMLS_DC ); +void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); +// send all the stream data +void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); +// called when a bound output string parameter is to be destroyed +void sqlsrv_output_param_dtor( zval* data ); +// called when a bound stream parameter is to be destroyed. +void sqlsrv_stream_dtor( zval* data ); +bool is_streamable_type( SQLINTEGER sql_type ); + +} + +// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. +sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), + conn( c ), + executed( false ), + past_fetch_end( false ), + current_results( NULL ), + cursor_type( SQL_CURSOR_FORWARD_ONLY ), + has_rows( false ), + fetch_called( false ), + last_field_index( -1 ), + past_next_result_end( false ), + param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte + send_streams_at_exec( true ), + current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), + current_stream_read( 0 ), + query_timeout( QUERY_TIMEOUT_INVALID ), + buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), + active_stream( NULL ) +{ + // initialize the input string parameters array (which holds zvals) + core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); + + // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) + ZVAL_NEW_ARR( ¶m_streams ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); + + // initialize the (input only) datetime parameters of converted date time objects to strings + array_init( ¶m_datetime_buffers ); + + // initialize the output string parameters (which holds sqlsrv_output_param structures) + ZVAL_NEW_ARR( &output_params ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); + + // initialize the field cache + ZVAL_NEW_ARR( &field_cache ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); +} + +// desctructor for sqlsrv statement. +sqlsrv_stmt::~sqlsrv_stmt( void ) +{ + if( active_stream ) { + TSRMLS_FETCH(); + close_active_stream( this TSRMLS_CC ); + } + + // delete any current results + if( current_results ) { + current_results->~sqlsrv_result_set(); + efree( current_results ); + current_results = NULL; + } + + invalidate(); + zval_ptr_dtor( ¶m_input_strings ); + zval_ptr_dtor( &output_params ); + zval_ptr_dtor( ¶m_streams ); + zval_ptr_dtor( ¶m_datetime_buffers ); + zval_ptr_dtor( &field_cache ); +} + + +// centralized place to release (without destroying the hash tables +// themselves) all the parameter data that accrues during the +// execution phase. +void sqlsrv_stmt::free_param_data( TSRMLS_D ) +{ + SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, + "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); + zend_hash_clean( Z_ARRVAL( param_input_strings )); + zend_hash_clean( Z_ARRVAL( output_params )); + zend_hash_clean( Z_ARRVAL( param_streams )); + zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); + zend_hash_clean( Z_ARRVAL( field_cache )); +} + + +// 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( TSRMLS_D ) +{ + this->fetch_called = false; + this->has_rows = false; + this->past_next_result_end = false; + this->past_fetch_end = false; + this->last_field_index = -1; + + // delete any current results + if( current_results ) { + current_results->~sqlsrv_result_set(); + efree( current_results ); + current_results = NULL; + } + + // create a new result set + if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { + current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); + } + else { + current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); + } +} + +// core_sqlsrv_create_stmt +// Common code to allocate a statement from either driver. Returns a valid driver statement object or +// throws an exception if an error occurs. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// stmt_factory - factory method to create a statement. +// options_ht - A HashTable of user provided options to be set on the statement. +// valid_stmt_opts - An array of valid driver supported statement options. +// err - callback for error handling +// driver - reference to caller +// Return +// Returns the created statement + +sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, + const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) +{ + sqlsrv_malloc_auto_ptr stmt; + SQLHANDLE stmt_h = SQL_NULL_HANDLE; + + try { + + core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); + + stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); + + stmt->conn = conn; + + // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free + // in the catch block below. + stmt_h = SQL_NULL_HANDLE; + + // process the options array given to core_sqlsrv_prepare. + if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + for( zend_hash_internal_pointer_reset( options_ht ); + zend_hash_has_more_elements( options_ht ) == SUCCESS; + zend_hash_move_forward( options_ht )) { + + zend_string *key = NULL; + zend_ulong index = -1; + zval* value_z = NULL; + + int type = zend_hash_get_current_key( options_ht, &key, &index); + + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); + + core::sqlsrv_zend_hash_get_current_data( *(stmt->conn), options_ht, value_z TSRMLS_CC ); + + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + + // if the key didn't match, then return the error to the script. + // The driver layer should ensure that the key is valid. + DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); + + // perform the actions the statement option needs done. + (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + } + + zend_hash_internal_pointer_end( options_ht ); + } + + sqlsrv_stmt* return_stmt = stmt; + stmt.transferred(); + + return return_stmt; + } + catch( core::CoreException& ) + { + if( stmt ) { + + conn->set_last_error( stmt->last_error() ); + stmt->~sqlsrv_stmt(); + } + + // if allocating the handle failed before the statement was allocated, free the handle + if( stmt_h != SQL_NULL_HANDLE) { + ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); + } + + throw; + } + catch( ... ) { + + DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); + } +} + + +// core_sqlsrv_bind_param +// Binds a parameter using SQLBindParameter. It allocates memory and handles other details +// in translating between the driver and ODBC. +// Parameters: +// param_num - number of the parameter, 0 based +// param_z - zval of the parameter +// php_out_type - type to return for output parameter +// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) +// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) +// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale +// Return: +// Nothing, though an exception is thrown if an error occurs +// The php type of the parameter is taken from the zval. +// The sql type is given as a hint if the driver provides it. + +void core_sqlsrv_bind_param(sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, + SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, + SQLSMALLINT decimal_digits TSRMLS_DC ) +{ + SQLSMALLINT c_type; + SQLPOINTER buffer = NULL; + SQLLEN buffer_len = 0; + + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, + "core_sqlsrv_bind_param: Invalid parameter direction." ); + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, + "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); + + try { + + // check is only < because params are 0 based + CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { + throw core::CoreException(); + } + + // resize the statements array of int_ptrs if the parameter isn't already set. + if( stmt->param_ind_ptrs.size() < static_cast(param_num + 1) ) { + stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA ); + } + SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ]; + + zval* param_ref = param_z; + if ( Z_ISREF_P( param_z ) ) { + ZVAL_DEREF( param_z ); + } + bool zval_was_null = (Z_TYPE_P( param_z ) == IS_NULL); + bool zval_was_bool = (Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE); + // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we + // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so + // we always let that match if they want a string back. + if( direction == SQL_PARAM_INPUT_OUTPUT ) { + bool match = false; + switch( php_out_type ) { + case SQLSRV_PHPTYPE_INT: + if( zval_was_null || zval_was_bool ) { + convert_to_long( param_z ); + } + match = Z_TYPE_P( param_z ) == IS_LONG; + break; + case SQLSRV_PHPTYPE_FLOAT: + if( zval_was_null ) { + convert_to_double( param_z ); + } + match = Z_TYPE_P( param_z ) == IS_DOUBLE; + break; + case SQLSRV_PHPTYPE_STRING: + // anything can be converted to a string + convert_to_string( param_z ); + match = true; + break; + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); + break; + default: + SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); + break; + } + CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { + throw core::CoreException(); + } + } + + // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so + // when the buffer is filled, the type is correct + if( direction == SQL_PARAM_OUTPUT ) { + switch( php_out_type ) { + case SQLSRV_PHPTYPE_INT: + convert_to_long( param_z ); + break; + case SQLSRV_PHPTYPE_FLOAT: + convert_to_double( param_z ); + break; + case SQLSRV_PHPTYPE_STRING: + convert_to_string( param_z ); + break; + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); + break; + default: + SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); + break; + } + } + + SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || + ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || + encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); + + // if the sql type is unknown, then set the default based on the PHP type passed in + if( sql_type == SQL_UNKNOWN_TYPE ) { + default_sql_type(stmt, param_num, param_z, encoding, sql_type TSRMLS_CC); + } + + // if the size is unknown, then set the default based on the PHP type passed in + if( column_size == SQLSRV_UNKNOWN_SIZE ) { + default_sql_size_and_scale( stmt, (unsigned int)param_num, param_z, encoding, column_size, decimal_digits TSRMLS_CC ); + } + + // determine the ODBC C type + c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); + + // set the buffer based on the PHP parameter type + switch( Z_TYPE_P( param_z )) { + + case IS_NULL: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + ind_ptr = SQL_NULL_DATA; + buffer = NULL; + buffer_len = 0; + } + break; + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + { + buffer = ¶m_z->value; + buffer_len = sizeof( param_z->value.lval ); + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned + sqlsrv_output_param output_param( param_ref, int(param_num), zval_was_bool ); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + } + } + break; + case IS_DOUBLE: + { + buffer = ¶m_z->value; + buffer_len = sizeof(Z_DVAL_P(param_z)); + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned + sqlsrv_output_param output_param(param_ref, int(param_num), false ); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + } + } + break; + case IS_STRING: + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) + if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { + + zval wbuffer_z; + ZVAL_NULL(&wbuffer_z); + + bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + param_num + 1, get_last_error_message() ) { + throw core::CoreException(); + } + buffer = Z_STRVAL_P( &wbuffer_z ); + buffer_len = Z_STRLEN_P( &wbuffer_z ); + core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); + } + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, + // we reallocate the string if it's interned + if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { + core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + } + + // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) + // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, + // convert it to wchar first + if( direction == SQL_PARAM_INPUT_OUTPUT && + (c_type == SQL_C_WCHAR || + (c_type == SQL_C_BINARY && + (sql_type == SQL_WCHAR || + sql_type == SQL_WVARCHAR || + sql_type == SQL_WLONGVARCHAR )))) { + + bool converted = convert_input_param_to_utf16( param_z, param_z ); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + param_num + 1, get_last_error_message() ) { + throw core::CoreException(); + } + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + ind_ptr = buffer_len; + } + + // since this is an output string, assure there is enough space to hold the requested size and + // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) + resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, + buffer, buffer_len TSRMLS_CC ); + + // save the parameter to be adjusted and/or converted after the results are processed + sqlsrv_output_param output_param( param_ref, encoding, param_num, (SQLUINTEGER)buffer_len ); + + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + + // For output parameters, if we set the column_size to be same as the buffer_len, + // than if there is a truncation due to the data coming from the server being + // greater than the column_size, we don't get any truncation error. In order to + // avoid this silent truncation, we set the column_size to be "MAX" size for + // string types. This will guarantee that there is no silent truncation for + // output parameters. + if( direction == SQL_PARAM_OUTPUT ) { + + switch( sql_type ) { + + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WVARCHAR: + column_size = SQL_SS_LENGTH_UNLIMITED; + break; + + default: + break; + } + } + } + break; + case IS_RESOURCE: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + sqlsrv_stream stream_encoding( param_z, encoding ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + core::sqlsrv_zend_hash_index_update_mem(*stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC); + buffer = reinterpret_cast( param_num ); + Z_TRY_ADDREF_P(param_z); // so that it doesn't go away while we're using it + buffer_len = 0; + ind_ptr = SQL_DATA_AT_EXEC; + } + break; + case IS_OBJECT: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + zval function_z; + zval buffer_z; + zval format_z; + zval params[1]; + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); + + bool valid_class_name_found = false; + + zend_class_entry *class_entry = Z_OBJCE_P(param_z TSRMLS_CC); + + while( class_entry != NULL ) { + + if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && + stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { + valid_class_name_found = true; + break; + } + + else { + + // Check the parent + class_entry = class_entry->parent; + } + } + + CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + + // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' + // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' + // sql type, it lacks the timezone. + if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + DateTime::DATETIMEOFFSET_FORMAT_LEN); + } + else if( sql_type == SQL_TYPE_DATE ) { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); + } + else { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); + } + // call the DateTime::format member function to convert the object to a string that SQL Server understands + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); + params[0] = format_z; + // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the + // DateTime object and $format_z is the format string. + int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + buffer = Z_STRVAL( buffer_z ); + zr = add_next_index_zval( &(stmt->param_datetime_buffers), &buffer_z ); + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + buffer_len = Z_STRLEN( buffer_z ) - 1; + ind_ptr = buffer_len; + break; + } + case IS_ARRAY: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); + break; + default: + DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " + "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); + break; + } + + if( zval_was_null ) { + ind_ptr = SQL_NULL_DATA; + } + + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); + } + catch( core::CoreException& e ) { + stmt->free_param_data( TSRMLS_C ); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + throw e; + } +} + + +// core_sqlsrv_execute +// Executes the statement previously prepared +// Parameters: +// stmt - the core sqlsrv_stmt structure that contains the ODBC handle +// Return: +// true if there is data, false if there is not + +void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) +{ + try { + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + SQLRETURN r; + + if( sql ) { + + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len = 0; + if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { + wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); + wsql_string[0] = L'\0'; + wsql_len = 0; + } + else { + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : + stmt->encoding() ); + wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), + sql_len, &wsql_len ); + CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + get_last_error_message() ) { + throw core::CoreException(); + } + } + r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); + } + else { + r = core::SQLExecute( stmt TSRMLS_CC ); + } + + // if data is needed (streams were bound) and they should be sent at execute time, then do so now + if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { + + send_param_streams( stmt TSRMLS_CC ); + } + + stmt->new_result_set( TSRMLS_C ); + stmt->executed = true; + + // if all the data has been sent and no data was returned then finalize the output parameters + if( stmt->send_streams_at_exec && (r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { + + finalize_output_parameters( stmt TSRMLS_CC ); + } + + } + catch( core::CoreException& e ) { + + // if the statement executed but failed in a subsequent operation before returning, + // we need to cancel the statement + if( stmt->executed ) { + SQLCancel( stmt->handle() ); + // stmt->executed = false; should this be reset if something fails? + } + + throw e; + } +} + + +// core_sqlsrv_fetch +// Moves the cursor according to the parameters (by default, moves to the next row) +// Parameters: +// stmt - the sqlsrv_stmt of the cursor +// fetch_orientation - method to move the cursor +// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) +// Returns: +// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the +// user scrolls past a non-scrollable result set + +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) +{ + // pre-condition check + SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, + "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); + + try { + + // clear the field cache of the previous fetch + zend_hash_clean( Z_ARRVAL( stmt->field_cache )); + + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { + throw core::CoreException(); + } + + SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { + throw core::CoreException(); + } + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + // if the statement has rows and is not scrollable but doesn't yet have + // fetch_called, this must be the first time we've called sqlsrv_fetch. + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { + stmt->fetch_called = true; + return true; + } + + // move to the record requested. For absolute records, we use a 0 based offset, so +1 since + // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. + SQLRETURN r = stmt->current_results->fetch( fetch_orientation, + ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 + TSRMLS_CC ); + if( r == SQL_NO_DATA ) { + // if this is a forward only cursor, mark that we've passed the end so future calls result in an error + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + stmt->past_fetch_end = true; + } + return false; + } + + // 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; + stmt->has_rows = true; // since we made it this far, we must have at least one row + } + catch (core::CoreException& e) { + throw e; + } + catch ( ... ) { + DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); + } + + return true; +} + + +// Retrieves metadata for a field of a prepared statement. +// Parameters: +// colno - the index of the field for which to return the metadata. columns are 0 based in PDO +// Return: +// A field_meta_data* consisting of the field metadata. + +field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) +{ + // pre-condition check + SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); + + sqlsrv_malloc_auto_ptr meta_data; + SQLSMALLINT field_name_len = 0; + + meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); + meta_data->field_name = static_cast( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 )); + + try { + core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len, + &(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale), + &(meta_data->field_is_nullable) TSRMLS_CC ); + } + catch( core::CoreException& e ) { + throw e; + } + + // depending on field type, we add the values into size or precision/scale. + switch( meta_data->field_type ) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_BIT: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + { + meta_data->field_precision = meta_data->field_size; + meta_data->field_size = 0; + break; + } + default: { + break; + } + } + + // Set the field name lenth + meta_data->field_name_len = field_name_len; + + field_meta_data* result_field_meta_data = meta_data; + meta_data.transferred(); + return result_field_meta_data; +} + + +// core_sqlsrv_get_field +// Return the value of a column from ODBC +// Parameters: +// stmt - the sqlsrv_stmt from which to retrieve the column +// field_index - 0 based index for the column to retrieve +// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in +// field_value - pointer to the data retrieved +// field_len - length of the data in the field_value buffer +// Returns: +// Nothing, excpetion thrown if an error occurs + +void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, + __out void** field_value, __out SQLLEN* field_len, bool cache_field, + __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ) +{ + try { + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + // if the field has been retrieved before, return the previous result + field_cache* cached = NULL; + if (NULL != (cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), static_cast(field_index))))) { + // the field value is NULL + if( cached->value == NULL ) { + *field_value = NULL; + *field_len = 0; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } + } + else { + + *field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); + memcpy( *field_value, cached->value, cached->len ); + if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING ) { + // prevent the 'string not null terminated' warning + reinterpret_cast( *field_value )[ cached->len ] = '\0'; + } + *field_len = cached->len; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = static_cast( cached->type.typeinfo.type ); } + } + return; + } + + sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; + + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; + + // Make sure that the statement was executed and not just prepared. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they + // may also be retrieved. + if( cache_field && ( field_index - stmt->last_field_index ) >= 2 ) { + sqlsrv_phptype invalid; + invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { + SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, + "Field already cached." ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, + sqlsrv_php_type_out TSRMLS_CC ); + // delete the value returned since we only want it cached, not the actual value + if( *field_value ) { + efree( *field_value ); + *field_value = NULL; + *field_len = 0; + } + } + } + + // If the php type was not specified set the php type to be the default type. + if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { + + // Get the SQL type of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + + // Get the length of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + + // Get the corresponding php type from the sql type. + sqlsrv_php_type = stmt->sql_type_to_php_type( (SQLINTEGER)sql_field_type, (SQLUINTEGER)sql_field_len, prefer_string ); + } + + // Verify that we have an acceptable type to convert. + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + throw core::CoreException(); + } + + if( sqlsrv_php_type_out != NULL ) + *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + + // Retrieve the data + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + + // if the user wants us to cache the field, we'll do it + if( cache_field ) { + field_cache cache( *field_value, *field_len, sqlsrv_php_type ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + } + } + + catch( core::CoreException& e) { + throw e; + } +} + +// core_sqlsrv_has_any_result +// return if any result set or rows affected message is waiting +// to be consumed and moved over by sqlsrv_next_result. +// Parameters: +// stmt - The statement object on which to check for results. +// Return: +// true if any results are present, false otherwise. + +bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // Use SQLNumResultCols to determine if we have rows or not. + SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + // use SQLRowCount to determine if there is a rows status waiting + SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + return (num_cols != 0) || (rows_affected > 0); +} + +// core_sqlsrv_next_result +// Advances to the next result set from the last executed query +// Parameters +// stmt - the sqlsrv_stmt structure +// Returns +// Nothing, exception thrown if problem occurs + +void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) +{ + try { + + // make sure that the statement has been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { + throw core::CoreException(); + } + + close_active_stream( stmt TSRMLS_CC ); + + SQLRETURN r; + if( throw_on_errors ) { + r = core::SQLMoreResults( stmt TSRMLS_CC ); + } + else { + r = SQLMoreResults( stmt->handle() ); + } + + if( r == SQL_NO_DATA ) { + + if( &(stmt->output_params) && finalize_output_params ) { + // if we're finished processing result sets, handle the output parameters + finalize_output_parameters( stmt TSRMLS_CC ); + } + + // mark we are past the end of all results + stmt->past_next_result_end = true; + return; + } + + stmt->new_result_set( TSRMLS_C ); + } + catch( core::CoreException& e ) { + + SQLCancel( stmt->handle() ); + throw e; + } +} + + +// core_sqlsrv_post_param +// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement +// Parameters: +// stmt - the sqlsrv_stmt structure +// param_num - 0 based index of the parameter +// param_z - parameter value itself. +// Returns: +// Nothing, exception thrown if problem occurs + +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) +{ + SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); + + // if the parameter was an input string, delete it from the array holding input parameter strings + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); + } + + // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams + // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it + // with sqlsrv_send_stream_data. + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { + sqlsrv_stream* stream_encoding = NULL; + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); + } +} + +//Calls SQLSetStmtAttr to set a cursor. +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, SQLULEN cursor_type TSRMLS_DC ) +{ + try { + + switch( cursor_type ) { + + case SQL_CURSOR_STATIC: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_DYNAMIC: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_KEYSET_DRIVEN: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_FORWARD_ONLY: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQLSRV_CURSOR_BUFFERED: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); + break; + } + + stmt->cursor_type = cursor_type; + + } + catch( core::CoreException& ) { + throw; + } +} + +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); + } + + core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); +} + +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) +{ + if( limit <= 0 ) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); + } + + stmt->buffered_query_limit = limit; +} + + +// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout +// which accepts timeout parameter as a long. If the zval is not of type long +// than throws error. +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + try { + + // validate the value + if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { + + convert_to_string( value_z ); + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); + } + + core_sqlsrv_set_query_timeout( stmt, (long)Z_LVAL_P( value_z ) TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } +} + +// Overloaded. Accepts the timeout as a long. +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); + + // set the statement attribute + core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + + // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which + // is represented by -1. + long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); + + // set the LOCK_TIMEOUT on the server. + char lock_timeout_sql[ 32 ]; + int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", + lock_timeout ); + + SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), + "stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." ); + + core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); + + stmt->query_timeout = timeout; + } + catch( core::CoreException& ) { + throw; + } +} + +void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + TSRMLS_C; + + // zend_is_true does not fail. It either returns true or false. + stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; +} + + +// core_sqlsrv_send_stream_packet +// 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. +// return_value is what should be returned to the script if it is +// given. Any errors that occur are posted here. +// Parameters: +// stmt - query to send the next packet for +// Returns: +// true if more data remains to be sent, false if all data processed + +bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt 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_stream.stream_z == NULL ) { + + if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { + + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream_read = 0; + return false; + } + } + + try { + + // get the stream from the zval we bound + php_stream* param_stream = NULL; + core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); + + // 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_stream_read == 0 ) { + // send an empty string, which is what a 0 length does. + char buff[1]; // temp storage to hand to SQLPutData + core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); + } + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream_read = 0; + } + // read the data from the stream, send it via SQLPutData and track how much we've sent. + else { + char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character + std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); + + if (read > UINT_MAX) + { + LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); + throw core::CoreException(); + } + + stmt->current_stream_read += (unsigned int)read; + if( read > 0 ) { + // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character + // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it + // twice. + // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion + // since all other MBCS supported by SQL Server are 2 byte maximum size. + if( stmt->current_stream.encoding == CP_UTF8 ) { + + // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a + // expansion of 2x the UTF-8 size. + wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate + int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, + buffer, int(read), wbuffer, int(sizeof( wbuffer ) / sizeof( wchar_t ))); + if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { + + // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more + // in, then reattempt the conversion. If it fails the second time, then an error is returned. + size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); + // read the missing bytes + size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, + need_to_read ); + // if the bytes couldn't be read, then we return an error + CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { + throw core::CoreException(); + } + // try the conversion again with the complete character + wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, + buffer, int(read + new_read), wbuffer, int(sizeof( wbuffer ) / sizeof( wchar_t ))); + // something else must be wrong if it failed + CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { + throw core::CoreException(); + } + } + core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC ); + } + else { + core::SQLPutData( stmt, buffer, read TSRMLS_CC ); + } + } + } + + } + catch( core::CoreException& e ) { + stmt->free_param_data( TSRMLS_C ); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + SQLCancel( stmt->handle() ); + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); + stmt->current_stream_read = 0; + throw e; + } + + return true; +} + + +void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) +{ + TSRMLS_C; + + // This implementation should never get called. + DIE( "Not implemented." ); +} + +void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); +} + + +// 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; + } + + php_stream* stream = NULL; + + // 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 ); + + SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: 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. + + SQLSRV_ASSERT( stmt->active_stream == NULL, "close_active_stream: Active stream not closed." ); + +} + +// local routines not shared by other files (arranged alphabetically) + +namespace { + +bool is_streamable_type( SQLLEN 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; +} + +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC ) +{ + try { + + switch( sql_type ) { + // for types that are fixed in size or for which the size is unknown, return the display size. + case SQL_BIGINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_GUID: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_REAL: + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_BINARY: + case SQL_CHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_SS_XML: + case SQL_SS_UDT: + case SQL_WLONGVARCHAR: + case SQL_DATETIME: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + { + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); + break; + } + + // for wide char types for which the size is known, return the octet length instead, since it will include the + // the number of bytes necessary for the string, not just the characters + case SQL_WCHAR: + case SQL_WVARCHAR: + { + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); + break; + } + + default: + DIE ( "Unexpected SQL type encountered in calc_string_size." ); + } + } + catch( core::CoreException& e ) { + throw e; + } +} + + +// calculates how many characters were cut off from the end of a buffer when reading +// in UTF-8 encoded text + +size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) +{ + const char* last_char = buffer + buffer_end - 1; + size_t need_to_read = 0; + + // rewind until we are at the byte that starts the cut off character + while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { + --last_char; + ++need_to_read; + } + + // determine how many bytes we need to read in based on the number of bytes in the character + // (# of high bits set) versus the number of bytes we've already read. + switch( *last_char & UTF8_NBYTESEQ_MASK ) { + case UTF8_2BYTESEQ_TAG1: + case UTF8_2BYTESEQ_TAG2: + need_to_read = 1 - need_to_read; + break; + case UTF8_3BYTESEQ_TAG: + need_to_read = 2 - need_to_read; + break; + case UTF8_4BYTESEQ_TAG: + need_to_read = 3 - need_to_read; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); + break; + } + + return need_to_read; +} + + +// Caller is responsible for freeing the memory allocated for the field_value. +// The memory allocation has to happen in the core layer because otherwise +// the driver layer would have to calculate size of the field_value +// to decide the amount of memory allocation. +void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ) +{ + try { + + close_active_stream( stmt TSRMLS_CC ); + + // make sure that fetch is called before trying to retrieve. + CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { + throw core::CoreException(); + } + + // make sure that fields are not retrieved incorrectly. + CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, + stmt->last_field_index ) { + throw core::CoreException(); + } + + switch( sqlsrv_php_type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_INT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + + SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + field_len, true /*handle_warning*/ TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( *field_len == SQL_NULL_DATA ) { + *field_value = NULL; + break; + } + + *field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + + case SQLSRV_PHPTYPE_FLOAT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), + field_len, true /*handle_warning*/ TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException (); + } + + if( *field_len == SQL_NULL_DATA ) { + *field_value = NULL; + break; + } + + *field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + + case SQLSRV_PHPTYPE_STRING: + { + get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + 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: + { + char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + zval params[1]; + zval field_value_temp_z; + zval function_z; + zval_auto_ptr return_value_z; + + ZVAL_UNDEF( &field_value_temp_z ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( params ); + return_value_z = (zval *)sqlsrv_malloc( sizeof(zval) ); + ZVAL_UNDEF( return_value_z ); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, + MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException (); + } + + if( *field_len == SQL_NULL_DATA ) { + ZVAL_NULL( return_value_z ); + *field_value = reinterpret_cast( return_value_z.get() ); + return_value_z.transferred(); + break; + } + + // Convert the string date to a DateTime object + core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); + core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") -1 ); + params[0] = field_value_temp_z; + + if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, + params TSRMLS_CC ) == FAILURE ) { + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED ); + } + + *field_value = reinterpret_cast( return_value_z.get() ); + return_value_z.transferred(); + 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: + { + + zval_auto_ptr return_value_z; + return_value_z = (zval *)sqlsrv_malloc(sizeof(zval*)); + php_stream* stream = NULL; + sqlsrv_stream* ss = NULL; + SQLLEN sql_type; + + SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { + throw core::CoreException(); + } + + stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + + CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { + throw core::CoreException(); + } + + ss = static_cast( stream->abstract ); + ss->stmt = stmt; + ss->field_index = field_index; + ss->sql_type = static_cast( sql_type ); + ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + + // turn our stream into a zval to be returned + php_stream_to_zval( stream, return_value_z ); + + // mark this as our active stream + stmt->active_stream = return_value_z; + *field_value = reinterpret_cast( return_value_z.get() ); + return_value_z.transferred(); + break; + } + + case SQLSRV_PHPTYPE_NULL: + *field_value = NULL; + *field_len = 0; + break; + + default: + DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); + break; + } + + // sucessfully retrieved the field, so update our last retrieved field + if( stmt->last_field_index < field_index ) { + stmt->last_field_index = field_index; + } + } + catch( core::CoreException& e ) { + throw e; + } +} + + +// check_for_next_stream_parameter +// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise +// returns false +bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC ) +{ + int stream_index = 0; + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stream* stream_encoding = NULL; + zval* param_z = NULL; + + // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param + r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); + // if no more data, we've exhausted the bound parameters, so return that we're done + if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + + // we're all done, so return false + return false; + } + + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + + // pull out the sqlsrv_encoding struct + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); + SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error + + param_z = stream_encoding->stream_z; + + // make the next stream current + stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); + stmt->current_stream_read = 0; + + // there are more parameters + return true; +} + + +// utility routine to convert an input parameter from UTF-8 to UTF-16 + +bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) +{ + SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, + "convert_input_param_z called with invalid parameter states" ); + + const char* buffer = Z_STRVAL_P( input_param_z ); + std::size_t buffer_len = Z_STRLEN_P( input_param_z ); + int wchar_size; + + if (buffer_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + + // if the string is empty, then just return that the conversion succeeded as + // MultiByteToWideChar will "fail" on an empty string. + if( buffer_len == 0 ) { + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); + return true; + } + + // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string + wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, + reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); + + // if there was a problem determining the size of the string, return false + if( wchar_size == 0 ) { + return false; + } + sqlsrv_malloc_auto_ptr wbuffer; + wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); + // convert the utf-8 string to a wchar string in the new buffer + int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), + static_cast( buffer_len ), wbuffer, wchar_size ); + // if there was a problem converting the string, then free the memory and return false + if( r == 0 ) { + return false; + } + + // null terminate the string, set the size within the zval, and return success + wbuffer[ wchar_size ] = L'\0'; + core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), + wchar_size * sizeof( wchar_t ) ); + sqlsrv_free(wbuffer); + wbuffer.transferred(); + + return true; +} + +// returns the ODBC C type constant that matches the PHP type and encoding given + +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) +{ + SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; + int php_type = Z_TYPE_P( param_z ); + + switch( php_type ) { + + case IS_NULL: + switch( encoding ) { + // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type + // is a binary type, than the server expects the sql_type to be binary type as well, otherwise + // an error stating "Implicit conversion not allowed.." is thrown by the server. + // For all other server types, setting the sql_type to sql_char works fine. + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + default: + sql_c_type = SQL_C_CHAR; + break; + } + break; + case IS_TRUE: + case IS_FALSE: + 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: + switch( encoding ) { + case SQLSRV_ENCODING_CHAR: + sql_c_type = SQL_C_CHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + case CP_UTF8: + sql_c_type = SQL_C_WCHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); + break; + } + 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 core_sqlsrv_bind_param. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + sql_c_type = SQL_C_CHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } + + return sql_c_type; +} + + +// given a zval and encoding, determine the appropriate sql type + +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + __out SQLSMALLINT& sql_type TSRMLS_DC ) +{ + sql_type = SQL_UNKNOWN_TYPE; + int php_type = Z_TYPE_P(param_z); + switch( php_type ) { + + case IS_NULL: + switch( encoding ) { + // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, + // if the server type is a binary type, than the server expects the sql_type to be binary type + // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the + // server. For all other server types, setting the sql_type to sql_char works fine. + case SQLSRV_ENCODING_BINARY: + sql_type = SQL_BINARY; + break; + default: + sql_type = SQL_CHAR; + break; + } + break; + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + sql_type = SQL_INTEGER; + break; + case IS_DOUBLE: + sql_type = SQL_FLOAT; + break; + case IS_RESOURCE: + case IS_STRING: + switch( encoding ) { + case SQLSRV_ENCODING_CHAR: + sql_type = SQL_VARCHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_type = SQL_VARBINARY; + break; + case CP_UTF8: + sql_type = SQL_WVARCHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); + break; + } + 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 the calling function. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + // if the user is sending this type to SQL Server 2005 or earlier, make it appear + // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET + // since these are the date types of the highest precision for their respective server versions + if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { + sql_type = SQL_TYPE_TIMESTAMP; + } + else { + sql_type = SQL_SS_TIMESTAMPOFFSET; + } + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } + +} + + +// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) + +void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, + __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ) +{ + int php_type = Z_TYPE_P( param_z ); + column_size = 0; + decimal_digits = 0; + + switch( php_type ) { + + case IS_NULL: + column_size = 1; + break; + // size is not necessary for these types, they are inferred by ODBC + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + case IS_DOUBLE: + case IS_RESOURCE: + break; + case IS_STRING: + { + SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char )); + if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { + column_size = SQL_SERVER_MAX_TYPE_SIZE; + } + else { + column_size = Z_STRLEN_P( param_z ); + } + 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 the calling function. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + // if the user is sending this type to SQL Server 2005 or earlier, make it appear + // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET + // since these are the date types of the highest precision for their respective server versions + if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { + column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; + decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; + } + else { + column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; + decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; + } + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } +} + +void field_cache_dtor( zval* data_z ) +{ + field_cache* cache = reinterpret_cast(Z_PTR_P(data_z)); + if( cache->value ) + { + sqlsrv_free( cache->value ); + } +} + + +// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output +// parameters will be present until all results are processed (since output parameters can depend on results +// while being processed). This function updates the lengths of output parameter strings from the ind_ptr +// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. +// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server + +void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + if( Z_ISUNDEF(stmt->output_params) ) + return; + + bool converted = true; + HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + for( zend_hash_internal_pointer_reset( params_ht ); + zend_hash_has_more_elements( params_ht ) == SUCCESS; + zend_hash_move_forward( params_ht ) ) { + sqlsrv_output_param* output_param = NULL; + core::sqlsrv_zend_hash_get_current_data_ptr(*stmt, params_ht, (void*&) output_param TSRMLS_CC); + zval* value_z = Z_REFVAL_P( output_param->param_z ); + switch( Z_TYPE_P( value_z )) { + case IS_STRING: + { + // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter + char* str = Z_STRVAL_P( value_z ); + SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; + if( str_len == SQL_NULL_DATA ) { + ZVAL_NULL( value_z ); + continue; + } + + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch( output_param->encoding ) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 + break; + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; + break; + case SQLSRV_ENCODING_BINARY: + null_size = 0; + break; + default: + SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); + break; + } + CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { + throw core::CoreException(); + } + + // if it's not in the 8 bit encodings, then it's in UTF-16 + if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { + bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { + // ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated + // so we do that here if the length of the returned data is less than the original allocation. The + // original allocation null terminates the buffer already. + str[ str_len ] = '\0'; + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + else { + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + } + break; + case IS_LONG: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { + ZVAL_NULL( value_z ); + } + else if( output_param->is_bool ) { + convert_to_boolean( value_z ); + } + else + { + ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); + } + break; + case IS_DOUBLE: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { + ZVAL_NULL( value_z ); + } + break; + default: + DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + break; + } + value_z = NULL; + } + + // empty the hash table since it's been processed + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + return; +} + +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ) +{ + SQLRETURN r; + SQLSMALLINT c_type; + SQLLEN sql_field_type = 0; + SQLSMALLINT extra = 0; + SQLLEN field_len_temp; + SQLLEN sql_display_size = 0; + char* field_value_temp = NULL; + + try { + + DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, + "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); + } + + // Set the C type and account for null characters at the end of the data. + switch( sqlsrv_php_type.typeinfo.encoding ) { + case CP_UTF8: + c_type = SQL_C_WCHAR; + extra = sizeof( SQLWCHAR ); + break; + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + extra = 0; + break; + default: + c_type = SQL_C_CHAR; + extra = sizeof( SQLCHAR ); + break; + } + + // Get the SQL type of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + + // 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_temp = INITIAL_FIELD_STRING_LEN; + + field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); + + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException (); + } + + if( field_len_temp == SQL_NULL_DATA ) { + *field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( r == SQL_SUCCESS_WITH_INFO ) { + + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + + if( is_truncated_warning( state ) ) { + + SQLLEN dummy_field_len; + + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // in every pass, we double the allocation size to retrieve all the contents. + if( field_len_temp == SQL_NO_TOTAL ) { + + // reset the field_len_temp + field_len_temp = INITIAL_FIELD_STRING_LEN; + + do { + SQLLEN initial_field_len = field_len_temp; + // Double the size. + field_len_temp *= 2; + + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + + field_len_temp -= initial_field_len; + + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); + + // 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_temp += dummy_field_len; + else + field_len_temp += initial_field_len; + + if( r == SQL_SUCCESS_WITH_INFO ) { + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + } + + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + } + else { + + // We got the field_len_temp from SQLGetData call. + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + + // We have already recieved INITIAL_FIELD_STRING_LEN size data. + field_len_temp -= INITIAL_FIELD_STRING_LEN; + + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); + + if( dummy_field_len == SQL_NULL_DATA ) { + *field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException (); + } + + field_len_temp += INITIAL_FIELD_STRING_LEN; + } + + } // if( is_truncation_warning ( state ) ) + else { + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + } + } // if( r == SQL_SUCCESS_WITH_INFO ) + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException (); + } + } + } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) + + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + + // only allow binary retrievals for char and binary types. All others get a string converted + // to the encoding type they asked for. + + // null terminator + if( c_type == SQL_C_CHAR ) { + sql_display_size += sizeof( SQLCHAR ); + } + + // For WCHAR multiply by sizeof(WCHAR) and include the null terminator + else if( c_type == SQL_C_WCHAR ) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); + } + + field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); + + // get the data + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, + &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + CHECK_SQL_ERROR( r, stmt ) { + throw core::CoreException(); + } + CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException (); + } + + if( field_len_temp == SQL_NULL_DATA ) { + *field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException (); + } + } + } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + + else { + + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } + + *field_value = field_value_temp; + *field_len = field_len_temp; + + // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP + // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. + // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary + // operator to set add 1 to fill the null terminator + field_value_temp[field_len_temp] = '\0'; + } + + catch( core::CoreException& ) { + + *field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + catch ( ... ) { + + *field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + +} + + +// return the option from the stmt_opts array that matches the key. If no option found, +// NULL is returned. + +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) +{ + for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { + + // if we find the key we're looking for, return it + if( key == stmt_opts[ i ].key ) { + return &stmt_opts[ i ]; + } + } + + return NULL; // no option found +} + +// is_fixed_size_type +// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary + +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; +} + +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 + || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + return true; + } + break; + } + } + + return false; +} + + +// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z +// is updated to have the new buffer with the correct size and its reference is incremented. The output +// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and +// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter + +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, + SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, + SQLLEN& buffer_len TSRMLS_DC ) +{ + SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); + buffer_len = Z_STRLEN_P( param_z ); + SQLLEN expected_len; + SQLLEN buffer_null_extra; + SQLLEN elem_size; + SQLLEN without_null_len; + + // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, + // as is a n(var)char/ntext field being returned as a binary field. + elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; + + // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning + expected_len = column_size * elem_size + elem_size; + + // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations + buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; + + // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter + without_null_len = column_size * elem_size; + + // increment to include the null terminator since the Zend length doesn't include the null terminator + buffer_len += elem_size; + + // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new + // length. + if( buffer_len < expected_len ) { + SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, + "Integer overflow/underflow caused a corrupt field length." ); + + // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since + // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about + // not having a NULL terminator on a string. + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); + + // A zval string len doesn't include the null. This calculates the length it should be + // regardless of whether the ODBC type contains the NULL or not. + + // null terminate the string to avoid a warning in debug PHP builds + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); + + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + } + + buffer = Z_STRVAL_P(param_z); + + // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which + // may be less than the size of the buffer since the output may be more than the input. If it is greater, + // than the error 22001 is returned by ODBC. + if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) { + stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra); + } +} + +// output parameters have their reference count incremented so that they do not disappear +// while the query is executed and processed. They are saved in the statement so that +// their reference count may be decremented later (after results are processed) + +void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) +{ + HashTable* param_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong paramno = static_cast(param.param_num); + core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof(sqlsrv_output_param)); + Z_TRY_ADDREF_P(param.param_z); // we have a reference to the param +} + + +// send all the stream data + +void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } +} + + +// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed +void sqlsrv_output_param_dtor( zval* data ) +{ + sqlsrv_output_param *output_param = reinterpret_cast( Z_PTR_P(data) ); + zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold +} + +// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed +void sqlsrv_stream_dtor(zval* data ) +{ + sqlsrv_stream* stream_encoding = reinterpret_cast( Z_PTR_P(data) ); + zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold +} + +} diff --git a/pdo_sqlsrv/core_stream.cpp b/pdo_sqlsrv/core_stream.cpp new file mode 100644 index 00000000..d2cb386e --- /dev/null +++ b/pdo_sqlsrv/core_stream.cpp @@ -0,0 +1,264 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_stream.cpp +// +// Contents: Implementation of PHP streams for reading SQL Server data +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include + +namespace { + +// close a stream and free the PHP resources used by it + +int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) +{ + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was 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 ); + + // there is no active stream + ss->stmt->active_stream = NULL; + + sqlsrv_free( 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 ) +{ + SQLLEN read = 0; + SQLSMALLINT c_type = SQL_C_CHAR; + char* get_data_buffer = buf; + sqlsrv_malloc_auto_ptr temp_buf; + + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); + + try { + + if( stream->eof ) { + return 0; + }; + + switch( ss->encoding ) { + case SQLSRV_ENCODING_CHAR: + c_type = SQL_C_CHAR; + break; + + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + break; + + case CP_UTF8: + { + c_type = SQL_C_WCHAR; + count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes + if( count > PHP_STREAM_BUFFER_SIZE ) { + count = PHP_STREAM_BUFFER_SIZE; + } + + // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 + temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); + get_data_buffer = temp_buf; + break; + } + + default: + DIE( "Unknown encoding type when reading from a stream" ); + break; + } + + SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); + + CHECK_SQL_ERROR( r, ss->stmt ) { + stream->eof = 1; + throw core::CoreException(); + } + + // 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 || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { + stream->eof = 1; + } + + // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator + // if it's not a binary encoded field + if( r == SQL_SUCCESS_WITH_INFO ) { + + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + + ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + + if( read == SQL_NO_TOTAL ) { + SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " + "did not occur." ); + } + + if( is_truncated_warning( state ) ) { + switch( c_type ) { + + // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, + // SQLGetData truncates the data to BufferLength less the length of null-termination character. + case SQL_C_BINARY: + read = count; + break; + case SQL_C_WCHAR: + read = ( count % 2 == 0 ? count - 2 : count - 3 ); + break; + case SQL_C_CHAR: + read = count - 1; + break; + default: + DIE( "sqlsrv_stream_read: should have never reached in this switch case."); + break; + } + } + else { + CHECK_SQL_WARNING( r, ss->stmt ); + } + } + + // if the encoding is UTF-8 + if (c_type == SQL_C_WCHAR) { + + count *= 2; // undo the shift to use the full buffer + + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + DWORD flags = 0; + + // convert to UTF-8 + if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } + + if ( count > INT_MAX || (read >> 1) > INT_MAX ) + { + LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + + int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), + int(read >> 1), buf, int(count), NULL, NULL ); + + if( enc_len == 0 ) { + + stream->eof = 1; + THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); + } + + read = enc_len; + } + + return read; + } + + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); + return 0; + } +} + +// 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 +}; + +// 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 const char*, __in const char* mode, + int options, __in zend_string **, 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 + + sqlsrv_malloc_auto_ptr ss; + + ss = static_cast( sqlsrv_malloc( 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; +} + +// 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. of the stream +php_stream_wrapper g_sqlsrv_stream_wrapper = { + &sqlsrv_stream_wrapper_ops, + NULL, + 0 +}; diff --git a/pdo_sqlsrv/core_util.cpp b/pdo_sqlsrv/core_util.cpp new file mode 100644 index 00000000..d533d281 --- /dev/null +++ b/pdo_sqlsrv/core_util.cpp @@ -0,0 +1,400 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_util.cpp +// +// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers +// +// Comments: Mostly error handling and some type handling +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include + +namespace { + +// *** internal constants *** +log_callback g_driver_log; +// internal error that says that FormatMessage failed +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; +// buffer used to hold a formatted log message prior to actually logging it. +char last_err_msg[ 2048 ]; // 2k to hold the error messages + +// routine used by utf16_string_from_mbcs_string +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, + __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, + unsigned int utf16_len ); +} + +// SQLSTATE for all internal errors +SQLCHAR IMSSP[] = "IMSSP"; + +// SQLSTATE for all internal warnings +SQLCHAR SSPWARN[] = "01SSP"; + +// 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 TSRMLS_DC, const char* msg, ...) +{ + SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); + + va_list args; + va_start( args, msg ); + + g_driver_log( severity TSRMLS_CC, msg, &args ); + + va_end( args ); +} + +void core_sqlsrv_register_logger( log_callback driver_logger ) +{ + g_driver_log = driver_logger; +} + + +// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new +// length in the len parameter. If no errors occurred during convertion, true is returned and the original +// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed +// and false is returned. + +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) +{ + SQLSRV_ASSERT( string != NULL, "String must be specified" ); + + if (validate_string(*string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16( encoding, + reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); + + if (result) + { + sqlsrv_free( *string ); + *string = outString; + len = outLen; + } + + return result; +} + +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) +{ + char* string = Z_STRVAL_P(value_z); + + if (validate_string(string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16(encoding, + reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); + + if (result) + { + core::sqlsrv_zval_stringl(value_z, outString, outLen); + sqlsrv_free(outString); + len = outLen; + } + + return result; +} + +bool validate_string(char* string, SQLLEN& len) +{ + SQLSRV_ASSERT(string != NULL, "String must be specified"); + + //for the empty string, we simply returned we converted it + if (len == 0 && string[0] == '\0') { + return true; + } + + if ((len / sizeof(wchar_t)) > INT_MAX) + { + LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + + return false; +} + +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) +{ + SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); + SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); + SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); + + if (cchInLen == 0 && inString[0] == L'\0') { + *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); + *outString[0] = '\0'; + cchOutLen = 0; + return true; + } + + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + DWORD flags = 0; + if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } + + // calculate the number of characters needed + cchOutLen = WideCharToMultiByte( encoding, flags, + inString, cchInLen, + NULL, 0, NULL, NULL ); + if( cchOutLen == 0 ) { + return false; + } + + // Create a buffer to fit the encoded string + char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); + int rc = WideCharToMultiByte( encoding, flags, + inString, cchInLen, + newString, static_cast(cchOutLen), NULL, NULL ); + if( rc == 0 ) { + cchOutLen = 0; + sqlsrv_free( newString ); + return false; + } + + *outString = newString; + newString[cchOutLen] = '\0'; // null terminate the encoded string + + return true; +} + +// thin wrapper around convert_string_from_default_encoding that handles +// allocation of the destination string. An empty string passed in returns +// failure since it's a failure case for convert_string_from_default_encoding. +wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, + unsigned int* utf16_len ) +{ + *utf16_len = (mbcs_len + 1); + wchar_t* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( wchar_t ))); + *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, + utf16_string, *utf16_len ); + if( *utf16_len == 0 ) { + // we preserve the error and reset it because sqlsrv_free resets the last error + DWORD last_error = GetLastError(); + sqlsrv_free( utf16_string ); + SetLastError( last_error ); + return NULL; + } + + return utf16_string; +} + +// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the +// errno is 1 based. It returns it as an array with 3 members: +// 1/SQLSTATE) sqlstate +// 2/code) driver specific error code +// 3/message) driver specific error message +// The fetch type determines if the indices are numeric, associative, or both. + +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity + TSRMLS_DC ) +{ + SQLHANDLE h = ctx.handle(); + SQLSMALLINT h_type = ctx.handle_type(); + + if( h == NULL ) { + return false; + } + + zval* ssphp_z = NULL; + int zr = SUCCESS; + zval* temp = NULL; + SQLRETURN r = SQL_SUCCESS; + SQLSMALLINT wmessage_len = 0; + SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ]; + SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; + SQLSRV_ENCODING enc = ctx.encoding(); + + switch( h_type ) { + + case SQL_HANDLE_STMT: + { + sqlsrv_stmt* stmt = static_cast( &ctx ); + if( stmt->current_results != NULL ) { + + error = stmt->current_results->get_diag_rec( record_number ); + // don't use the CHECK* macros here since it will trigger reentry into the error handling system + if( error == NULL ) { + return false; + } + break; + } + + // convert the error into the encoding of the context + if( enc == SQLSRV_ENCODING_DEFAULT ) { + enc = stmt->conn->encoding(); + } + } + + + default: + + error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); + r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, + SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); + // don't use the CHECK* macros here since it will trigger reentry into the error handling system + if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + return false; + } + + SQLLEN sqlstate_len = 0; + convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); + + SQLLEN message_len = 0; + convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); + break; + } + + + // log the error first + LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); + LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); + LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); + + error->format = false; + + return true; +} + +// format and return a driver specfic error +void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, + sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) +{ + // allocate space for the formatted message + formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); + formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); + formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, + reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); + if( rc == 0 ) { + strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, + reinterpret_cast( INTERNAL_FORMAT_ERROR )); + } + + strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, + reinterpret_cast( custom_error->sqlstate )); + formatted_error->native_code = custom_error->native_code; + + // log the error + LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); + LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); + LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); +} + +DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +{ + va_list format_args; + va_start( format_args, format ); + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args ); + + va_end( format_args ); + + return rc; +} + +// return an error message for GetLastError using FormatMessage. +// this function returns the msg pointer so that it may be used within +// another function call such as handle_error +const char* get_last_error_message( DWORD last_error ) +{ + if( last_error == 0 ) { + last_error = GetLastError(); + } + + DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + last_err_msg, sizeof( last_err_msg ), NULL ); + + if( r == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); + } + + return last_err_msg; +} + + +// die +// Terminate the PHP request with an error message +// We use this function rather than php_error directly because we use the FormatMessage syntax in most other +// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were +// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without +// proper information. Rather than convert those messages and try and remember the difference between LOG and +// DIE, it is simpler to make the format syntax common between them. +void die( const char* msg, ... ) +{ + va_list format_args; + va_start( format_args, msg ); + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args ); + + va_end( format_args ); + + if( rc == 0 ) { + php_error( E_ERROR, reinterpret_cast( INTERNAL_FORMAT_ERROR )); + } + + php_error( E_ERROR, last_err_msg ); +} + +namespace { + +// convert from the default encoding specified by the "CharacterSet" +// connection option to UTF-16. mbcs_len and utf16_len are sizes in +// bytes. The return is the number of UTF-16 characters in the string +// returned in utf16_out_string. An empty string passed in will result as +// a failure since MBTWC returns 0 for both an empty string and failure +// to convert. +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, + unsigned int utf16_len ) +{ + unsigned int win_encoding = CP_ACP; + switch( php_encoding ) { + case SQLSRV_ENCODING_CHAR: + win_encoding = CP_ACP; + break; + // this shouldn't ever be set + case SQLSRV_ENCODING_BINARY: + DIE( "Invalid encoding." ); + break; + default: + win_encoding = php_encoding; + break; + } + unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, + utf16_out_string, utf16_len ); + if( required_len == 0 ) { + return 0; + } + utf16_out_string[ required_len ] = '\0'; + + return required_len; +} + +} diff --git a/pdo_sqlsrv/msodbcsql.h b/pdo_sqlsrv/msodbcsql.h new file mode 100644 index 00000000..a16bf59a --- /dev/null +++ b/pdo_sqlsrv/msodbcsql.h @@ -0,0 +1,2343 @@ +//----------------------------------------------------------------------------- +// File: msodbcsql.h +// +// Copyright: Copyright (c) Microsoft Corporation +// +// Contents: ODBC driver for SQL Server specific definitions. +// +//----------------------------------------------------------------------------- +#ifndef __msodbcsql_h__ +#define __msodbcsql_h__ + +#if !defined(SQLODBC_VER) +#define SQLODBC_VER 1100 +#endif + +#if SQLODBC_VER >= 1100 + +#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server" +#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" + +#define SQLODBC_FILE_NAME_ANSI "msodbcsql" +#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11" +#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll" + +#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server" +#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server" + +#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql" +#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11" +#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll" + +// define the character type agnostic constants +#if defined(_UNICODE) || defined(UNICODE) + +#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE +#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE +#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE +#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE + +#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE +#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE +#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE + +#else // _UNICODE || UNICODE + +#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI +#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI + +#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI +#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI +#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI + +#endif // _UNICODE || UNICODE + +#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER + +#endif // SQLODBC_VER + +#ifndef __sqlncli_h__ + +#if !defined(SQLNCLI_VER) +#define SQLNCLI_VER 1100 +#endif + +#if SQLNCLI_VER >= 1100 + +#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0" +#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0" +#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" + +#define SQLNCLI_FILE_NAME_ANSI "sqlncli" +#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11" +#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll" + +#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0" +#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0" +#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" + +#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" +#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11" +#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll" + +#elif SQLNCLI_VER >= 1000 + +#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0" +#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0" +#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" + +#define SQLNCLI_FILE_NAME_ANSI "sqlncli" +#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10" +#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll" + +#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0" +#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0" +#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" + +#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" +#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10" +#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll" + +#else + +#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client" + +#define SQLNCLI_FILE_NAME_ANSI "sqlncli" +#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli" +#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll" + +#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client" +#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client" + +#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" +#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli" +#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll" + +#endif // SQLNCLI_VER >= 1100 + +// define the character type agnostic constants +#if defined(_UNICODE) || defined(UNICODE) + +#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE +#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE +#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE +#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE + +#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE +#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE +#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE + + +#else // _UNICODE || UNICODE + +#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI +#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI +#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI +#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI + +#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI +#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI +#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI + +#endif // _UNICODE || UNICODE + +#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER + + +#ifdef ODBCVER + +#ifdef __cplusplus +extern "C" { +#endif + +// max SQL Server identifier length +#define SQL_MAX_SQLSERVERNAME 128 + +// SQLSetConnectAttr driver specific defines. +// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. +// Connection attributes +#define SQL_COPT_SS_BASE 1200 +#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only +#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare +#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login +#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact +#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata +#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction +#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction +#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections +#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data +#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data +#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds. +#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies. +#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging. +#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk. +#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers +#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings +#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection +#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation +#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database +#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL +#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data +#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection +#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server +#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login +#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level +#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate +#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN +#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN +#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection +#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated +#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID +// Define old names +#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD +#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP +#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY +#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS + +// SQLSetStmtAttr SQL Server Native Client driver specific defines. +// Statement attributes +#define SQL_SOPT_SS_BASE 1225 +#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging +#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only +#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns +#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option +#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions +#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options +#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator +#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary +#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout +#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text +#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name +#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types +#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls +#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE +// Define old names +#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING +#define SQL_COPT_SS_BASE_EX 1240 +#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation +#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request. +#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion. +#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check +#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K) +#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet +#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent +#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover +#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER + +// SQLColAttributes driver specific defines. +// SQLSetDescField/SQLGetDescField driver specific defines. +// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. +#define SQL_CA_SS_BASE 1200 +#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype +#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype +#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders +#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol +#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen +#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute +#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return +#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist +#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid +#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop +#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen +#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE) +#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE) +//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location. +#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars) +#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) +#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) +#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) + +// XML, CLR UDT, and table valued parameter related metadata +#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name +#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name +#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name +#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class +#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection +#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection +#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection +#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name +#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name +#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name + +// table valued parameter related metadata +#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed +#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key +#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order +#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal +#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter + +// sparse column related metadata +#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns + +// Legacy datetime related metadata +#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types + +#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36) + +// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE +#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option +// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP +#define SQL_UP_OFF 0L // Procedures won't be used for prepare +#define SQL_UP_ON 1L // Procedures will be used for prepare +#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped +#define SQL_UP_DEFAULT SQL_UP_ON +// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only +#define SQL_IS_OFF 0L // Integrated security isn't used +#define SQL_IS_ON 1L // Integrated security is used +#define SQL_IS_DEFAULT SQL_IS_OFF +// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS +#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact +#define SQL_PC_ON 1L // Cursors remain open on SQLTransact +#define SQL_PC_DEFAULT SQL_PC_OFF +// Defines for use with SQL_COPT_SS_USER_DATA +#define SQL_UD_NOTSET NULL // No user data pointer set +// Defines for use with SQL_COPT_SS_TRANSLATE +#define SQL_XL_OFF 0L // Code page translation is not performed +#define SQL_XL_ON 1L // Code page translation is performed +#define SQL_XL_DEFAULT SQL_XL_ON +// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only +#define SQL_FB_OFF 0L // FallBack connections are disabled +#define SQL_FB_ON 1L // FallBack connections are enabled +#define SQL_FB_DEFAULT SQL_FB_OFF +// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only +#define SQL_BCP_OFF 0L // BCP is not allowed on connection +#define SQL_BCP_ON 1L // BCP is allowed on connection +#define SQL_BCP_DEFAULT SQL_BCP_OFF +// Defines for use with SQL_COPT_SS_QUOTED_IDENT +#define SQL_QI_OFF 0L // Quoted identifiers are enable +#define SQL_QI_ON 1L // Quoted identifiers are disabled +#define SQL_QI_DEFAULT SQL_QI_ON +// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only +#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled +#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled +#define SQL_AD_DEFAULT SQL_AD_ON +// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only +#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off +#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on +#define SQL_CN_DEFAULT SQL_CN_ON +// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING +#define SQL_TL_OFF 0L // No logging on text pointer ops +#define SQL_TL_ON 1L // Logging occurs on text pointer ops +#define SQL_TL_DEFAULT SQL_TL_ON +// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS +#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden +#define SQL_HC_ON 1L // FOR BROWSE columns are exposed +#define SQL_HC_DEFAULT SQL_HC_OFF +// Defines for use with SQL_SOPT_SS_NOBROWSETABLE +#define SQL_NB_OFF 0L // NO_BROWSETABLE is off +#define SQL_NB_ON 1L // NO_BROWSETABLE is on +#define SQL_NB_DEFAULT SQL_NB_OFF +// Defines for use with SQL_SOPT_SS_REGIONALIZE +#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions +#define SQL_RE_ON 1L // Regionalization occurs on output character conversions +#define SQL_RE_DEFAULT SQL_RE_OFF +// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS +#define SQL_CO_OFF 0L // Clear all cursor options +#define SQL_CO_FFO 1L // Fast-forward cursor will be used +#define SQL_CO_AF 2L // Autofetch on cursor open +#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch +#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors +#define SQL_CO_DEFAULT SQL_CO_OFF +//SQL_SOPT_SS_NOCOUNT_STATUS +#define SQL_NC_OFF 0L +#define SQL_NC_ON 1L +//SQL_SOPT_SS_DEFER_PREPARE +#define SQL_DP_OFF 0L +#define SQL_DP_ON 1L +//SQL_SOPT_SS_NAME_SCOPE +#define SQL_SS_NAME_SCOPE_TABLE 0L +#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L +#define SQL_SS_NAME_SCOPE_EXTENDED 2L +#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L +#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE +//SQL_COPT_SS_ENCRYPT +#define SQL_EN_OFF 0L +#define SQL_EN_ON 1L +//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE +#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L +#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L +//SQL_COPT_SS_BROWSE_CONNECT +#define SQL_MORE_INFO_NO 0L +#define SQL_MORE_INFO_YES 1L +//SQL_COPT_SS_BROWSE_CACHE_DATA +#define SQL_CACHE_DATA_NO 0L +#define SQL_CACHE_DATA_YES 1L +//SQL_COPT_SS_RESET_CONNECTION +#define SQL_RESET_YES 1L +//SQL_COPT_SS_WARN_ON_CP_ERROR +#define SQL_WARN_NO 0L +#define SQL_WARN_YES 1L +//SQL_COPT_SS_MARS_ENABLED +#define SQL_MARS_ENABLED_NO 0L +#define SQL_MARS_ENABLED_YES 1L +/* SQL_TXN_ISOLATION_OPTION bitmasks */ +#define SQL_TXN_SS_SNAPSHOT 0x00000020L + +// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER +#define SQL_SS_ORDER_UNSPECIFIED 0L +#define SQL_SS_DESCENDING_ORDER 1L +#define SQL_SS_ASCENDING_ORDER 2L +#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED + +// Driver specific SQL data type defines. +// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage. +#define SQL_SS_VARIANT (-150) +#define SQL_SS_UDT (-151) +#define SQL_SS_XML (-152) +#define SQL_SS_TABLE (-153) +#define SQL_SS_TIME2 (-154) +#define SQL_SS_TIMESTAMPOFFSET (-155) + +// Local types to be used with SQL_CA_SS_SERVER_TYPE +#define SQL_SS_TYPE_DEFAULT 0L +#define SQL_SS_TYPE_SMALLDATETIME 1L +#define SQL_SS_TYPE_DATETIME 2L + +// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. +#define SQL_C_TYPES_EXTENDED 0x04000L +#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0) +#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1) + +#ifndef SQLNCLI_NO_BCP +// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application +// and you want to exclude the BCP-related definitions in this header file. + +// SQL Server Data Type defines. +// New types for SQL 6.0 and later servers +#define SQLTEXT 0x23 +#define SQLVARBINARY 0x25 +#define SQLINTN 0x26 +#define SQLVARCHAR 0x27 +#define SQLBINARY 0x2d +#define SQLIMAGE 0x22 +#define SQLCHARACTER 0x2f +#define SQLINT1 0x30 +#define SQLBIT 0x32 +#define SQLINT2 0x34 +#define SQLINT4 0x38 +#define SQLMONEY 0x3c +#define SQLDATETIME 0x3d +#define SQLFLT8 0x3e +#define SQLFLTN 0x6d +#define SQLMONEYN 0x6e +#define SQLDATETIMN 0x6f +#define SQLFLT4 0x3b +#define SQLMONEY4 0x7a +#define SQLDATETIM4 0x3a +// New types for SQL 6.0 and later servers +#define SQLDECIMAL 0x6a +#define SQLNUMERIC 0x6c +// New types for SQL 7.0 and later servers +#define SQLUNIQUEID 0x24 +#define SQLBIGCHAR 0xaf +#define SQLBIGVARCHAR 0xa7 +#define SQLBIGBINARY 0xad +#define SQLBIGVARBINARY 0xa5 +#define SQLBITN 0x68 +#define SQLNCHAR 0xef +#define SQLNVARCHAR 0xe7 +#define SQLNTEXT 0x63 +// New types for SQL 2000 and later servers +#define SQLINT8 0x7f +#define SQLVARIANT 0x62 +// New types for SQL 2005 and later servers +#define SQLUDT 0xf0 +#define SQLXML 0xf1 +// New types for SQL 2008 and later servers +#define SQLTABLE 0xf3 +#define SQLDATEN 0x28 +#define SQLTIMEN 0x29 +#define SQLDATETIME2N 0x2a +#define SQLDATETIMEOFFSETN 0x2b +// Define old names +#define SQLDECIMALN 0x6a +#define SQLNUMERICN 0x6c +#endif // SQLNCLI_NO_BCP + +// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of +// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns +#define SQL_SS_LENGTH_UNLIMITED 0 + +// User Data Type definitions. +// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. +#define SQLudtBINARY 3 +#define SQLudtBIT 16 +#define SQLudtBITN 0 +#define SQLudtCHAR 1 +#define SQLudtDATETIM4 22 +#define SQLudtDATETIME 12 +#define SQLudtDATETIMN 15 +#define SQLudtDECML 24 +#define SQLudtDECMLN 26 +#define SQLudtFLT4 23 +#define SQLudtFLT8 8 +#define SQLudtFLTN 14 +#define SQLudtIMAGE 20 +#define SQLudtINT1 5 +#define SQLudtINT2 6 +#define SQLudtINT4 7 +#define SQLudtINTN 13 +#define SQLudtMONEY 11 +#define SQLudtMONEY4 21 +#define SQLudtMONEYN 17 +#define SQLudtNUM 10 +#define SQLudtNUMN 25 +#define SQLudtSYSNAME 18 +#define SQLudtTEXT 19 +#define SQLudtTIMESTAMP 80 +#define SQLudtUNIQUEIDENTIFIER 0 +#define SQLudtVARBINARY 4 +#define SQLudtVARCHAR 2 +#define MIN_USER_DATATYPE 256 +// Aggregate operator types. +// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. +#define SQLAOPSTDEV 0x30 // Standard deviation +#define SQLAOPSTDEVP 0x31 // Standard deviation population +#define SQLAOPVAR 0x32 // Variance +#define SQLAOPVARP 0x33 // Variance population +#define SQLAOPCNT 0x4b // Count +#define SQLAOPSUM 0x4d // Sum +#define SQLAOPAVG 0x4f // Average +#define SQLAOPMIN 0x51 // Min +#define SQLAOPMAX 0x52 // Max +#define SQLAOPANY 0x53 // Any +#define SQLAOPNOOP 0x56 // None +// SQLGetInfo driver specific defines. +// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage. +#define SQL_INFO_SS_FIRST 1199 +#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo +#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo +#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA +#ifdef UNICODE +#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW +#else +#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA +#endif + +// SQLGetDiagField driver specific defines. +// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage. +#define SQL_DIAG_SS_BASE (-1150) +#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) +#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) +#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) +#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) +#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) +// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. +// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage. +#define SQL_DIAG_DFC_SS_BASE (-200) +#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) +#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) +#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) +#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) +#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) +#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) +#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) +#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) +#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) +#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) +#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) +#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) +#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) +#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) +#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) +#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) +#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) +#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) +#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) +#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) +#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) +#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) +#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) +#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) +#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) +#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) +#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) +#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) +#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) +#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) +#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) +#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) +#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) +#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) +#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) +#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) +#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) +#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) +#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) +#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) +#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) +#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) +#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) +#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) +#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) +#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) +#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) +#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) +#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) +#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) +#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) +#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) + +// Severity codes for SQL_DIAG_SS_SEVERITY +#define EX_ANY 0 +#define EX_INFO 10 +#define EX_MAXISEVERITY EX_INFO +#define EX_MISSING 11 +#define EX_TYPE 12 +#define EX_DEADLOCK 13 +#define EX_PERMIT 14 +#define EX_SYNTAX 15 +#define EX_USER 16 +#define EX_RESOURCE 17 +#define EX_INTOK 18 +#define MAXUSEVERITY EX_INTOK +#define EX_LIMIT 19 +#define EX_CMDFATAL 20 +#define MINFATALERR EX_CMDFATAL +#define EX_DBFATAL 21 +#define EX_TABCORRUPT 22 +#define EX_DBCORRUPT 23 +#define EX_HARDWARE 24 +#define EX_CONTROL 25 +// Internal server datatypes - used when binding to SQL_C_BINARY +#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts +// DB-Library datatypes +#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte +#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte +#ifdef UNICODE +typedef wchar_t DBCHAR; +#else +typedef char DBCHAR; + +#endif +typedef short SQLSMALLINT; + +typedef unsigned short SQLUSMALLINT; + +typedef unsigned char DBBINARY; + +typedef unsigned char DBTINYINT; + +typedef short DBSMALLINT; + +typedef unsigned short DBUSMALLINT; + +typedef double DBFLT8; + +typedef unsigned char DBBIT; + +typedef unsigned char DBBOOL; + +typedef float DBFLT4; + +typedef DBFLT4 DBREAL; + +typedef UINT DBUBOOL; + +typedef struct dbmoney + { + LONG mnyhigh; + ULONG mnylow; + } DBMONEY; + +typedef struct dbdatetime + { + LONG dtdays; + ULONG dttime; + } DBDATETIME; + +typedef struct dbdatetime4 + { + USHORT numdays; + USHORT nummins; + } DBDATETIM4; + +typedef LONG DBMONEY4; + +#include // 8-byte structure packing + +// New Date Time Structures +// New Structure for TIME2 +typedef struct tagSS_TIME2_STRUCT +{ + SQLUSMALLINT hour; + SQLUSMALLINT minute; + SQLUSMALLINT second; + SQLUINTEGER fraction; +} SQL_SS_TIME2_STRUCT; +// New Structure for TIMESTAMPOFFSET +typedef struct tagSS_TIMESTAMPOFFSET_STRUCT +{ + SQLSMALLINT year; + SQLUSMALLINT month; + SQLUSMALLINT day; + SQLUSMALLINT hour; + SQLUSMALLINT minute; + SQLUSMALLINT second; + SQLUINTEGER fraction; + SQLSMALLINT timezone_hour; + SQLSMALLINT timezone_minute; +} SQL_SS_TIMESTAMPOFFSET_STRUCT; + +typedef struct tagDBTIME2 +{ + USHORT hour; + USHORT minute; + USHORT second; + ULONG fraction; +} DBTIME2; + +typedef struct tagDBTIMESTAMPOFFSET +{ + SHORT year; + USHORT month; + USHORT day; + USHORT hour; + USHORT minute; + USHORT second; + ULONG fraction; + SHORT timezone_hour; + SHORT timezone_minute; +} DBTIMESTAMPOFFSET; + +#include // restore original structure packing + +// Money value *10,000 +#define DBNUM_PREC_TYPE BYTE +#define DBNUM_SCALE_TYPE BYTE +#define DBNUM_VAL_TYPE BYTE + +#if (ODBCVER < 0x0300) +#define MAXNUMERICLEN 16 +typedef struct dbnumeric // Internal representation of NUMERIC data type +{ + DBNUM_PREC_TYPE precision; // Precision + DBNUM_SCALE_TYPE scale; // Scale + BYTE sign; // Sign (1 if positive, 0 if negative) + DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value +} DBNUMERIC; +typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type +#else // Use ODBC 3.0 definitions since same as DBLib +#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN +typedef SQL_NUMERIC_STRUCT DBNUMERIC; +typedef SQL_NUMERIC_STRUCT DBDECIMAL; +#endif // ODCBVER +#endif // MAXNUMERICLEN + +#ifndef INT +typedef int INT; +typedef LONG DBINT; +typedef DBINT * LPDBINT; +#ifndef _LPCBYTE_DEFINED +#define _LPCBYTE_DEFINED +typedef BYTE const* LPCBYTE; +#endif //_LPCBYTE_DEFINED +#endif // INT +/************************************************************************** +This struct is a global used for gathering statistical data on the driver. +Access to this structure is controlled via the pStatCrit; +***************************************************************************/ +typedef struct sqlperf +{ + // Application Profile Statistics + DWORD TimerResolution; + DWORD SQLidu; + DWORD SQLiduRows; + DWORD SQLSelects; + DWORD SQLSelectRows; + DWORD Transactions; + DWORD SQLPrepares; + DWORD ExecDirects; + DWORD SQLExecutes; + DWORD CursorOpens; + DWORD CursorSize; + DWORD CursorUsed; + LDOUBLE PercentCursorUsed; + LDOUBLE AvgFetchTime; + LDOUBLE AvgCursorSize; + LDOUBLE AvgCursorUsed; + DWORD SQLFetchTime; + DWORD SQLFetchCount; + DWORD CurrentStmtCount; + DWORD MaxOpenStmt; + DWORD SumOpenStmt; + // Connection Statistics + DWORD CurrentConnectionCount; + DWORD MaxConnectionsOpened; + DWORD SumConnectionsOpened; + DWORD SumConnectiontime; + LDOUBLE AvgTimeOpened; + // Network Statistics + DWORD ServerRndTrips; + DWORD BuffersSent; + DWORD BuffersRec; + DWORD BytesSent; + DWORD BytesRec; + // Time Statistics; + DWORD msExecutionTime; + DWORD msNetWorkServerTime; +} SQLPERF; +// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY +#define SQL_PERF_START 1 // Starts the driver sampling performance data. +#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data. +// The following are defines for SQL_COPT_SS_PERF_DATA_LOG +#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG") +// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG +#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG") +// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL +#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds + +#ifndef SQLNCLI_NO_BCP +// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application +// and you want to exclude the BCP-related definitions in this header file. + +// ODBC BCP prototypes and defines +// Return codes +#define SUCCEED 1 +#define FAIL 0 +#define SUCCEED_ABORT 2 +#define SUCCEED_ASYNC 3 +// Transfer directions +#define DB_IN 1 // Transfer from client to server +#define DB_OUT 2 // Transfer from server to client +// bcp_control option +#define BCPMAXERRS 1 // Sets max errors allowed +#define BCPFIRST 2 // Sets first row to be copied out +#define BCPLAST 3 // Sets number of rows to be copied out +#define BCPBATCH 4 // Sets input batch size +#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values +#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT +#define BCPODBC 7 // Sets ODBC canonical character output +#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on +#if SQLNCLI_VER < 1000 +#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on +#endif +#define BCPHINTSA 10 // Sets server BCP hints (ANSI string) +#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string) +#define BCPFILECP 12 // Sets clients code page for the file +#define BCPUNICODEFILE 13 // Sets that the file contains unicode header +#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically +#define BCPFILEFMT 15 // Sets file format version +#define BCPFMTXML 16 // Sets the format file type to xml +#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit) +#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit) +#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit) +#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec +// BCPFILECP values +// Any valid code page that is installed on the client can be passed plus: +#define BCPFILECP_ACP 0 // Data in file is in Windows code page +#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default) +#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion) +// bcp_collen definition +#define SQL_VARLEN_DATA (-10) // Use default length for column +// BCP column format properties +#define BCP_FMT_TYPE 0x01 +#define BCP_FMT_INDICATOR_LEN 0x02 +#define BCP_FMT_DATA_LEN 0x03 +#define BCP_FMT_TERMINATOR 0x04 +#define BCP_FMT_SERVER_COL 0x05 +#define BCP_FMT_COLLATION 0x06 +#define BCP_FMT_COLLATION_ID 0x07 +// bcp_setbulkmode properties +#define BCP_OUT_CHARACTER_MODE 0x01 +#define BCP_OUT_WIDE_CHARACTER_MODE 0x02 +#define BCP_OUT_NATIVE_TEXT_MODE 0x03 +#define BCP_OUT_NATIVE_MODE 0x04 + + + +// BCP functions +DBINT SQL_API bcp_batch (HDBC); +RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT); +RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT); +RETCODE SQL_API bcp_collen (HDBC, DBINT, INT); +RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT); +RETCODE SQL_API bcp_columns (HDBC, INT); +RETCODE SQL_API bcp_control (HDBC, INT, void *); +DBINT SQL_API bcp_done (HDBC); +RETCODE SQL_API bcp_exec (HDBC, LPDBINT); +RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *); +RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT); +RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT); +RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); +RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); +RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); +RETCODE SQL_API bcp_sendrow (HDBC); +RETCODE SQL_API bcp_setbulkmode (HDBC, INT, __in_bcount(cbField) void*, INT cbField, __in_bcount(cbRow) void *, INT cbRow); +RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); +RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); +RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); +CHAR* SQL_API dbprtypeA (INT); +WCHAR* SQL_API dbprtypeW (INT); +CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL); +WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL); + +#ifdef UNICODE +#define bcp_init bcp_initW +#define bcp_readfmt bcp_readfmtW +#define bcp_writefmt bcp_writefmtW +#define dbprtype dbprtypeW +#define bcp_gettypename bcp_gettypenameW +#define BCPHINTS BCPHINTSW +#else +#define bcp_init bcp_initA +#define bcp_readfmt bcp_readfmtA +#define bcp_writefmt bcp_writefmtA +#define dbprtype dbprtypeA +#define bcp_gettypename bcp_gettypenameA +#define BCPHINTS BCPHINTSA +#endif // UNICODE + +#endif // SQLNCLI_NO_BCP + +// The following options have been deprecated +#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0) +// Defines for use with SQL_FAST_CONNECT - only useable before connecting +#define SQL_FC_OFF 0L // Fast connect is off +#define SQL_FC_ON 1L // Fast connect is on +#define SQL_FC_DEFAULT SQL_FC_OFF +#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6) +#define SQL_AO_OFF 0L +#define SQL_AO_ON 1L +#define SQL_AO_DEFAULT SQL_AO_OFF +#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ODBCVER + + + +#ifdef __cplusplus +extern "C" { +#endif +#include + +//The following facilitates opening a handle to a SQL filestream +typedef enum _SQL_FILESTREAM_DESIRED_ACCESS { + SQL_FILESTREAM_READ = 0, + SQL_FILESTREAM_WRITE = 1, + SQL_FILESTREAM_READWRITE = 2 +} SQL_FILESTREAM_DESIRED_ACCESS; +#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L +#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L +#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L +#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L +#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L + + +HANDLE __stdcall OpenSqlFilestream ( + LPCWSTR FilestreamPath, + SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, + ULONG OpenOptions, + __in_bcount(FilestreamTransactionContextLength) + LPBYTE FilestreamTransactionContext, + SSIZE_T FilestreamTransactionContextLength, + PLARGE_INTEGER AllocationSize); +#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif //__sqlncli_h__ + +#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount +#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval +#ifdef SQL_COPT_SS_MAX_USED +#undef SQL_COPT_SS_MAX_USED +#endif // SQL_COPT_SS_MAX_USED +#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL + + +#ifndef _SQLUSERINSTANCE_H_ +#define _SQLUSERINSTANCE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Recommended buffer size to store a LocalDB connection string +#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260 + +// type definition for LocalDBCreateInstance function +typedef HRESULT __cdecl FnLocalDBCreateInstance ( + // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) + __in_z PCWSTR wszVersion, + // I the instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags +); + +// type definition for pointer to LocalDBCreateInstance function +typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; + +// type definition for LocalDBStartInstance function +typedef HRESULT __cdecl FnLocalDBStartInstance ( + // I the LocalDB instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags, + // O the buffer to store the connection string to the LocalDB instance + __out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, + // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is + // too small, has the buffer size required, in characters, including trailing null. + __inout_opt LPDWORD lpcchSqlConnection +); + +// type definition for pointer to LocalDBStartInstance function +typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; + +// Flags for the LocalDBFormatMessage function +#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L + +// type definition for LocalDBFormatMessage function +typedef HRESULT __cdecl FnLocalDBFormatMessage( + // I the LocalDB error code + __in HRESULT hrLocalDB, + // I Available flags: + // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, + // the error message will be truncated to fit into the buffer + __in DWORD dwFlags, + // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) + __in DWORD dwLanguageId, + // O the buffer to store the LocalDB error message + __out_ecount_z(*lpcchMessage) LPWSTR wszMessage, + // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is + // too small, has the buffer size required, in characters, including trailing null. If the function succeeds + // contains the number of characters in the message, excluding the trailing null + __inout LPDWORD lpcchMessage +); + +// type definition for function pointer to LocalDBFormatMessage function +typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage; + + +// MessageId: LOCALDB_ERROR_NOT_INSTALLED +// +// MessageText: +// +// LocalDB is not installed. +// +#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L) + +//--------------------------------------------------------------------- +// Function: LocalDBCreateInstance +// +// Description: This function will create the new LocalDB instance. +// +// Available Flags: +// No flags available. Reserved for future use. +// +// Return Values: +// S_OK, if the function succeeds +// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid +// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid +// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid +// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance +// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH +// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed +// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty +// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version +// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile% +// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved +// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed +// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified +// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created +// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. +// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted +// +FnLocalDBCreateInstance LocalDBCreateInstance; + +//--------------------------------------------------------------------- +// Function: LocalDBStartInstance +// +// Description: This function will start the given LocalDB instance. +// +// Return Values: +// S_OK, if the function succeeds +// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist +// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid +// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL +// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid +// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small +// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH + +// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved +// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed +// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified +// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created +// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. +// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted +// +FnLocalDBStartInstance LocalDBStartInstance; + +// type definition for LocalDBStopInstance function +typedef HRESULT __cdecl FnLocalDBStopInstance ( + // I the LocalDB instance name + __in_z PCWSTR pInstanceName, + // I Available flags: + // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately + // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option + __in DWORD dwFlags, + // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately + // without waiting for LocalDB instance to stop + __in ULONG ulTimeout +); + +// type definition for pointer to LocalDBStopInstance function +typedef FnLocalDBStopInstance* PFnLocalDBStopInstance; + +// Flags for the StopLocalDBInstance function +#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L +#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L + +//--------------------------------------------------------------------- +// Function: LocalDBStopInstance +// +// Description: This function will shutdown the given LocalDB instance. +// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately. +// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option. +// +// Return Values: +// S_OK, if the function succeeds +// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist +// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid +// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid +// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// +FnLocalDBStopInstance LocalDBStopInstance; + +// type definition for LocalDBDeleteInstance function +typedef HRESULT __cdecl FnLocalDBDeleteInstance ( + // I the LocalDB instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags +); + +// type definition for pointer to LocalDBDeleteInstance function +typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance; + +//--------------------------------------------------------------------- +// Function: LocalDBDeleteInstance +// +// Description: This function will remove the given LocalDB instance. If the given instance is running this function will +// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY. +// +// Return Values: +// S_OK, if the function succeeds +// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid +// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid +// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist +// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// +FnLocalDBDeleteInstance LocalDBDeleteInstance; + +// Function: LocalDBFormatMessage +// +// Description: This function will return the localized textual description for the given LocalDB error +// +// Available Flags: +// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer +// +// Return Value: +// S_OK, if the function succeeds +// +// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown +// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language) +// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown +// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid +// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag +// is not set +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// +FnLocalDBFormatMessage LocalDBFormatMessage; + +#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128 +#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME + +typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1]; +typedef TLocalDBInstanceName* PTLocalDBInstanceName; + +// type definition for LocalDBGetInstances function +typedef HRESULT __cdecl FnLocalDBGetInstances( + // O buffer for a LocalDB instance names + __out PTLocalDBInstanceName pInstanceNames, + // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, + // has the number of existing LocalDB instances + __inout LPDWORD lpdwNumberOfInstances +); + +// type definition for pointer to LocalDBGetInstances function +typedef FnLocalDBGetInstances* PFnLocalDBGetInstances; + +// Function: LocalDBGetInstances +// +// Description: This function returns names for all existing Local DB instances +// +// Usage Example: +// DWORD dwN = 0; +// LocalDBGetInstances(NULL, &dwN); + +// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName)); +// LocalDBGetInstances(insts, &dwN); + +// for (int i = 0; i < dwN; i++) +// wprintf(L"%s\n", insts[i]); +// +// Return values: +// S_OK, if the function succeeds +// +// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// +FnLocalDBGetInstances LocalDBGetInstances; + +// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15 +#define MAX_STRING_SID_LENGTH 186 + +#pragma pack(push) +#pragma pack(8) + +// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file. +// +typedef struct _LocalDBInstanceInfo +{ + DWORD cbLocalDBInstanceInfoSize; + TLocalDBInstanceName wszInstanceName; + BOOL bExists; + BOOL bConfigurationCorrupted; + BOOL bIsRunning; + DWORD dwMajor; + DWORD dwMinor; + DWORD dwBuild; + DWORD dwRevision; + FILETIME ftLastStartDateUTC; + WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE]; + BOOL bIsShared; + TLocalDBInstanceName wszSharedInstanceName; + WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1]; + BOOL bIsAutomatic; +} LocalDBInstanceInfo; + +#pragma pack(pop) + +typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; + +// type definition for LocalDBGetInstanceInfo function +typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( + // I the LocalDB instance name + __in_z PCWSTR wszInstanceName, + // O instance information + __out PLocalDBInstanceInfo pInfo, + // I Size of LocalDBInstanceInfo structure in bytes + __in DWORD cbInfo); + +// type definition for pointer to LocalDBGetInstances function +typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; + +// Function: LocalDBGetInstanceInfo +// +// Description: This function returns information about the given instance. +// +// Return values: +// S_OK, if the function succeeds +// +// ERROR_INVALID_PARAMETER, if some of the parameters is invalid +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details +// +FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo; + +// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots +// +#define MAX_LOCALDB_VERSION_LENGTH 43 + +typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1]; +typedef TLocalDBVersion* PTLocalDBVersion; + +// type definition for LocalDBGetVersions function +typedef HRESULT __cdecl FnLocalDBGetVersions( + // O buffer for installed LocalDB versions + __out PTLocalDBVersion pVersions, + // I/O on input has the number slots for versions in the pVersions buffer. On output, + // has the number of existing LocalDB versions + __inout LPDWORD lpdwNumberOfVersions +); + +// type definition for pointer to LocalDBGetVersions function +typedef FnLocalDBGetVersions* PFnLocalDBGetVersions; + +// Function: LocalDBGetVersions +// +// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor +// +// Usage Example: +// DWORD dwN = 0; +// LocalDBGetVersions(NULL, &dwN); + +// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion)); +// LocalDBGetVersions(insts, &dwN); + +// for (int i = 0; i < dwN; i++) +// wprintf(L"%s\n", insts[i]); +// +// Return values: +// S_OK, if the function succeeds +// +// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small +// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs. +// +FnLocalDBGetVersions LocalDBGetVersions; + +#pragma pack(push) +#pragma pack(8) + +// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file. +// +typedef struct _LocalDBVersionInfo +{ + DWORD cbLocalDBVersionInfoSize; + TLocalDBVersion wszVersion; + BOOL bExists; + DWORD dwMajor; + DWORD dwMinor; + DWORD dwBuild; + DWORD dwRevision; +} LocalDBVersionInfo; + +#pragma pack(pop) + +typedef LocalDBVersionInfo* PLocalDBVersionInfo; + +// type definition for LocalDBGetVersionInfo function +typedef HRESULT __cdecl FnLocalDBGetVersionInfo( + // I LocalDB version string + __in_z PCWSTR wszVersion, + // O version information + __out PLocalDBVersionInfo pVersionInfo, + // I Size of LocalDBVersionInfo structure in bytes + __in DWORD cbVersionInfo +); + +// type definition for pointer to LocalDBGetVersionInfo function +typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo; + +// Function: LocalDBGetVersionInfo +// +// Description: This function returns information about the given LocalDB version +// +// Return values: +// S_OK, if the function succeeds +// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred +// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid +// +FnLocalDBGetVersionInfo LocalDBGetVersionInfo; + +typedef HRESULT __cdecl FnLocalDBStartTracing(); +typedef FnLocalDBStartTracing* PFnLocalDBStartTracing; + +// Function: LocalDBStartTracing +// +// Description: This function will write in registry that Tracing sessions should be started for the current user. +// +// Return values: +// S_OK - on success +// Propper HRESULT in case of failure +// +FnLocalDBStartTracing LocalDBStartTracing; + +typedef HRESULT __cdecl FnLocalDBStopTracing(); +typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing; + +// Function: LocalDBStopTracing +// +// Description: This function will write in registry that Tracing sessions should be stopped for the current user. +// +// Return values: +// S_OK - on success +// Propper HRESULT in case of failure +// +FnLocalDBStopTracing LocalDBStopTracing; + +// type definition for LocalDBShareInstance function +typedef HRESULT __cdecl FnLocalDBShareInstance( + // I the SID of the LocalDB instance owner + __in_opt PSID pOwnerSID, + // I the private name of LocalDB instance which should be shared + __in_z PCWSTR wszPrivateLocalDBInstanceName, + // I the public shared name + __in_z PCWSTR wszSharedName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags); + +// type definition for pointer to LocalDBShareInstance function +typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; + +// Function: LocalDBShareInstance +// +// Description: This function will share the given private instance of the given user with the given shared name. +// This function has to be executed elevated. +// +// Return values: +// HRESULT +// +FnLocalDBShareInstance LocalDBShareInstance; + +// type definition for LocalDBUnshareInstance function +typedef HRESULT __cdecl FnLocalDBUnshareInstance( + // I the LocalDB instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags); + +// type definition for pointer to LocalDBUnshareInstance function +typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; + +// Function: LocalDBUnshareInstance +// +// Description: This function unshares the given LocalDB instance. +// If a shared name is given then that shared instance will be unshared. +// If a private name is given then we will check if the caller +// shares a private instance with the given private name and unshare it. +// +// Return values: +// HRESULT +// +FnLocalDBUnshareInstance LocalDBUnshareInstance; + +#ifdef __cplusplus +} // extern "C" +#endif + +#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS) +//--------------------------------------------------------------------- +// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS +// is defined. It provides an implementation of proxies for each of the LocalDB APIs. +// The proxy implementations use a common function to bind to entry points in the +// latest installed SqlUserInstance DLL, and then forward the requests. +// +// The current implementation loads the SqlUserInstance DLL on the first call into +// a proxy function. There is no provision for unloading the DLL. Note that if the +// process includes multiple binaries (EXE and one or more DLLs), each of them could +// load a separate instance of the SqlUserInstance DLL. +// +// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically. +// +// WARNING: these functions must not be called in DLL initialization, since a deadlock +// could result loading dependent DLLs. +//--------------------------------------------------------------------- + +// This macro provides the body for each proxy function. +// +#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn) + +// Structure and function to parse the "Installed Versions" registry subkeys +// +typedef struct { + DWORD dwComponent[2]; + WCHAR wszKeyName[256]; +} Version; + +// The following algorithm is intended to match, in part, the .NET Version class. +// A maximum of two components are allowed, which must be separated with a period. +// Valid: "11", "11.0" +// Invalid: "", ".0", "11.", "11.0." +// +static BOOL ParseVersion(Version * pVersion) +{ + pVersion->dwComponent[0] = 0; + pVersion->dwComponent[1] = 0; + WCHAR * pwch = pVersion->wszKeyName; + + for (int i = 0; i<2; i++) + { + LONGLONG llVal = 0; + BOOL fHaveDigit = FALSE; + + while (*pwch >= L'0' && *pwch <= L'9') + { + llVal = llVal * 10 + (*pwch++ - L'0'); + fHaveDigit = TRUE; + + if (llVal > 0x7fffffff) + { + return FALSE; + } + } + + if (!fHaveDigit) + return FALSE; + + pVersion->dwComponent[i] = (DWORD) llVal; + + if (*pwch == L'\0') + return TRUE; + + if (*pwch != L'.') + return FALSE; + + pwch++; + } + // If we get here, the version string was terminated with L'.', which is not valid + // + return FALSE; +} + +#include + +// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure. +// Note that the first-loaded API DLL for the process will be used until process termination: installation of +// a new version of the API will not be recognized after first load. +// +static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn) +{ + static volatile HMODULE hLocalDBDll = NULL; + + if (!hLocalDBDll) + { + LONG ec; + HKEY hkeyVersions = NULL; + HKEY hkeyVersion = NULL; + Version verHigh = {0}; + Version verCurrent; + DWORD cchKeyName; + DWORD dwValueType; + WCHAR wszLocalDBDll[MAX_PATH+1]; + DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk + HMODULE hLocalDBDllTemp = NULL; + + if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions))) + { + goto Cleanup; + } + + for (int i = 0; ; i++) + { + cchKeyName = 256; + if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL))) + { + if (ERROR_NO_MORE_ITEMS == ec) + { + break; + } + goto Cleanup; + } + + if (!ParseVersion(&verCurrent)) + { + continue; // invalid version syntax + } + + if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] || + (verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1])) + { + verHigh = verCurrent; + } + } + if (!verHigh.wszKeyName[0]) + { + // ec must be ERROR_NO_MORE_ITEMS here + // + assert(ec == ERROR_NO_MORE_ITEMS); + + // We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that + // LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists + // but it is empty. + // + ec = ERROR_FILE_NOT_FOUND; + goto Cleanup; + } + + if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion))) + { + goto Cleanup; + } + if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll))) + { + goto Cleanup; + } + if (dwValueType != REG_SZ) + { + ec = ERROR_INVALID_DATA; + goto Cleanup; + } + // Ensure string value null-terminated + // Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose + // + wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0'; + + hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll); + if (NULL == hLocalDBDllTemp) + { + ec = GetLastError(); + goto Cleanup; + } + if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL)) + { + // We were the winner: we gave away our DLL handle + // + hLocalDBDllTemp = NULL; + } + ec = ERROR_SUCCESS; +Cleanup: + if (hLocalDBDllTemp) + FreeLibrary(hLocalDBDllTemp); + if (hkeyVersion) + RegCloseKey(hkeyVersion); + if (hkeyVersions) + RegCloseKey(hkeyVersions); + + // Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing. + // In that case we should return the LocalDB specific error code + // + if (ec == ERROR_FILE_NOT_FOUND) + return LOCALDB_ERROR_NOT_INSTALLED; + + if (ec != ERROR_SUCCESS) + return HRESULT_FROM_WIN32(ec); + } + + FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn); + + if (!pfn) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + *pfnLocalDBFn = pfn; + return S_OK; +} + +// The following proxy functions forward calls to the latest LocalDB API DLL. +// + +HRESULT __cdecl +LocalDBCreateInstance ( + // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) + __in_z PCWSTR wszVersion, + // I the instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags +) +{ + LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); +} + +HRESULT __cdecl +LocalDBStartInstance( + // I the instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags, + // O the buffer to store the connection string to the LocalDB instance + __out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, + // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is + // too small, has the buffer size required, in characters, including trailing null. + __inout_opt LPDWORD lpcchSqlConnection +) +{ + LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); +} + +HRESULT __cdecl +LocalDBStopInstance ( + // I the instance name + __in_z PCWSTR pInstanceName, + // I Available flags: + // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately + // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option + __in DWORD dwFlags, + // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately + // without waiting for LocalDB instance to stop + __in ULONG ulTimeout +) +{ + LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); +} + +HRESULT __cdecl +LocalDBDeleteInstance ( + // I the instance name + __in_z PCWSTR pInstanceName, + // reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags +) +{ + LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); +} + +HRESULT __cdecl +LocalDBFormatMessage( + // I the LocalDB error code + __in HRESULT hrLocalDB, + // I Available flags: + // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, + // the error message will be truncated to fit into the buffer + __in DWORD dwFlags, + // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) + __in DWORD dwLanguageId, + // O the buffer to store the LocalDB error message + __out_ecount_z(*lpcchMessage) LPWSTR wszMessage, + // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is + // too small, has the buffer size required, in characters, including trailing null. If the function succeeds + // contains the number of characters in the message, excluding the trailing null + __inout LPDWORD lpcchMessage +) +{ + LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); +} + +HRESULT __cdecl +LocalDBGetInstances( + // O buffer with instance names + __out PTLocalDBInstanceName pInstanceNames, + // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, + // has the number of existing LocalDB instances + __inout LPDWORD lpdwNumberOfInstances +) +{ + LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); +} + +HRESULT __cdecl +LocalDBGetInstanceInfo( + // I the instance name + __in_z PCWSTR wszInstanceName, + // O instance information + __out PLocalDBInstanceInfo pInfo, + // I Size of LocalDBInstanceInfo structure in bytes + __in DWORD cbInfo +) +{ + LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); +} + +HRESULT __cdecl +LocalDBStartTracing() +{ + LOCALDB_PROXY(LocalDBStartTracing)(); +} + +HRESULT __cdecl +LocalDBStopTracing() +{ + LOCALDB_PROXY(LocalDBStopTracing)(); +} + +HRESULT __cdecl +LocalDBShareInstance( + // I the SID of the LocalDB instance owner + __in_opt PSID pOwnerSID, + // I the private name of LocalDB instance which should be shared + __in_z PCWSTR wszLocalDBInstancePrivateName, + // I the public shared name + __in_z PCWSTR wszSharedName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags) +{ + LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); +} + +HRESULT __cdecl +LocalDBGetVersions( + // O buffer for installed LocalDB versions + __out PTLocalDBVersion pVersions, + // I/O on input has the number slots for versions in the pVersions buffer. On output, + // has the number of existing LocalDB versions + __inout LPDWORD lpdwNumberOfVersions +) +{ + LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); +} + +HRESULT __cdecl +LocalDBUnshareInstance( + // I the LocalDB instance name + __in_z PCWSTR pInstanceName, + // I reserved for the future use. Currently should be set to 0. + __in DWORD dwFlags) +{ + LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); +} + +HRESULT __cdecl +LocalDBGetVersionInfo( + // I LocalDB version string + __in_z PCWSTR wszVersion, + // O version information + __out PLocalDBVersionInfo pVersionInfo, + // I Size of LocalDBVersionInfo structure in bytes + __in DWORD cbVersionInfo) +{ + LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); +} + +#endif + +#endif // _SQLUSERINSTANCE_H_ + +//----------------------------------------------------------------------------- +// File: sqluserinstancemsgs.mc +// +// Copyright: Copyright (c) Microsoft Corporation +//----------------------------------------------------------------------------- +#ifndef _LOCALDB_MESSAGES_H_ +#define _LOCALDB_MESSAGES_H_ +// Header section +// +// Section with the LocalDB messages +// +// +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +-+-+-+-+-+---------------------+-------------------------------+ +// |S|R|C|N|r| Facility | Code | +// +-+-+-+-+-+---------------------+-------------------------------+ +// +// where +// +// S - Severity - indicates success/fail +// +// 0 - Success +// 1 - Fail (COERROR) +// +// R - reserved portion of the facility code, corresponds to NT's +// second severity bit. +// +// C - reserved portion of the facility code, corresponds to NT's +// C field. +// +// N - reserved portion of the facility code. Used to indicate a +// mapped NT status value. +// +// r - reserved portion of the facility code. Reserved for internal +// use. Used to indicate HRESULT values that are not status +// values, but are instead message ids for display strings. +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// +#define FACILITY_LOCALDB 0x9C5 + + +// +// Define the severity codes +// +#define LOCALDB_SEVERITY_SUCCESS 0x0 +#define LOCALDB_SEVERITY_ERROR 0x2 + + +// +// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER +// +// MessageText: +// +// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. +// +#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L) + +// +// MessageId: LOCALDB_ERROR_INVALID_PARAMETER +// +// MessageText: +// +// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation. +// +#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION +// +// MessageText: +// +// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version. +// +#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L) + +// +// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER +// +// MessageText: +// +// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%). +// +#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG +// +// MessageText: +// +// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. +// +#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L) + +// +// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER +// +// MessageText: +// +// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. +// +#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L) + +// +// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY +// +// MessageText: +// +// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details. +// +#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L) + +// +// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE +// +// MessageText: +// +// The specified LocalDB instance does not exist. +// +#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L) + +// +// MessageId: LOCALDB_ERROR_INTERNAL_ERROR +// +// MessageText: +// +// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details. +// +#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L) + +// +// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY +// +// MessageText: +// +// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details. +// +#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L) + +// +// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED +// +// MessageText: +// +// Error occurred during LocalDB instance startup: SQL Server process failed to start. +// +#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT +// +// MessageText: +// +// LocalDB instance is corrupted. See the Windows Application event log for error details. +// +#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL) + +// +// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS +// +// MessageText: +// +// Error occurred during LocalDB instance startup: unable to create the SQL Server process. +// +#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL) + +// +// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION +// +// MessageText: +// +// The specified LocalDB version is not available on this computer. +// +#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL) + +// +// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID +// +// MessageText: +// +// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown. +// +#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED +// +// MessageText: +// +// Stop operation for LocalDB instance failed to complete within the specified time. +// +#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL) + +// +// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE +// +// MessageText: +// +// Error getting the localized error message. The specified error code is unknown. +// +#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L) + +// +// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED +// +// MessageText: +// +// The LocalDB version available on this workstation is lower than the requested LocalDB version. +// +#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_BUSY +// +// MessageText: +// +// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again. +// +#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L) + +// +// MessageId: LOCALDB_ERROR_INVALID_OPERATION +// +// MessageText: +// +// Default LocalDB instances cannot be created, stopped or deleted manually. +// +#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L) + +// +// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER +// +// MessageText: +// +// The buffer passed to the LocalDB instance API method has insufficient size. +// +#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L) + +// +// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT +// +// MessageText: +// +// Timeout occurred inside the LocalDB instance API method. +// +#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L) + +// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED. +// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API. +// +// +// MessageId: LOCALDB_ERROR_XEVENT_FAILED +// +// MessageText: +// +// Failed to start XEvent engine within the LocalDB Instance API. +// +#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L) + +// +// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED +// +// MessageText: +// +// Cannot create an automatic instance. See the Windows Application event log for error details. +// +#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L) + +// +// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN +// +// MessageText: +// +// Cannot create a shared instance. The specified shared instance name is already in use. +// +#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L) + +// +// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER +// +// MessageText: +// +// API caller is not LocalDB instance owner. +// +#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL) + +// +// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME +// +// MessageText: +// +// Specified LocalDB instance name is invalid. +// +#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED +// +// MessageText: +// +// The specified LocalDB instance is already shared with different shared name. +// +#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL) + +// +// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED +// +// MessageText: +// +// The specified LocalDB instance is not shared. +// +#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL) + +// +// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED +// +// MessageText: +// +// Administrator privileges are required in order to execute this operation. +// +#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL) + +// +// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES +// +// MessageText: +// +// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances. +// +#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL) + +// +// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH +// +// MessageText: +// +// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user. +// +#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L) + +// +// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES +// +// MessageText: +// +// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL". +// +#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L) + + // Detailed error descriptions +// +// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING +// +// MessageText: +// +// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1 +// +#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L) + +// +// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER +// +// MessageText: +// +// Cannot access LocalDB instance folder: %1 +// +#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L) + +// +// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG +// +// MessageText: +// +// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1 +// +#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L) + +// +// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING +// +// MessageText: +// +// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1 +// +#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L) + +// +// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG +// +// MessageText: +// +// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1 +// +#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L) + +// +// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID +// +// MessageText: +// +// Data directory for LocalDB instance is invalid: %1 +// +#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L) + +// +// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT +// +// MessageText: +// +// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4) +// +#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L) + +// +// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR +// +// MessageText: +// +// LocalDB instance API: XEvent error: %1 +// +#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L) + +// +// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED +// +// MessageText: +// +// LocalDB installation is corrupted. Reinstall the LocalDB. +// +#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L) + +// +// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION +// +// MessageText: +// +// LocalDB XEvent error: cannot determine %ProgramFiles% folder location. +// +#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L) + +// +// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE +// +// MessageText: +// +// LocalDB XEvent error: Cannot initialize XEvent engine. +// +#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL) + +// +// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE +// +// MessageText: +// +// LocalDB XEvent error: Cannot find XEvents configuration file: %1 +// +#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL) + +// +// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE +// +// MessageText: +// +// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1 +// HRESULT returned: %2 +// +#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL) + +// +// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG +// +// MessageText: +// +// LocalDB XEvent error: XEvents engine configuration file too long +// +#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL) + +// +// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED +// +// MessageText: +// +// CoInitializeEx API failed. HRESULT returned: %1 +// +#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL) + +// +// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID +// +// MessageText: +// +// LocalDB parent instance version is invalid: %1 +// +#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL) + +// +// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR +// +// MessageText: +// +// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5 +// +#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L) + +// +// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT +// +// MessageText: +// +// Function %1 returned %2 at line %3. +// +#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L) + +// +#endif // _LOCALDB_MESSAGES_H_ + +#endif //__msodbcsql_h__ diff --git a/pdo_sqlsrv/pdo_dbh.cpp b/pdo_sqlsrv/pdo_dbh.cpp new file mode 100644 index 00000000..968fa771 --- /dev/null +++ b/pdo_sqlsrv/pdo_dbh.cpp @@ -0,0 +1,1419 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// file: pdo_dbh.cpp +// +// Contents: Implements the PDO object for PDO_SQLSRV +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "pdo_sqlsrv.h" +#include +#include +#include + +#include +#include + + +typedef const zend_function_entry pdo_sqlsrv_function_entry; + +// *** internal variables and constants *** + +namespace { + +const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; +const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer +const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)"; +const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes + +// List of PDO supported connection options. +namespace PDOConnOptionNames { + +const char Server[] = "Server"; +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_Option[] = "MultipleActiveResultSets"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char WSID[] = "WSID"; + +} + +enum PDO_CONN_OPTIONS { + + PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, + +}; + +enum PDO_STMT_OPTIONS { + + PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC, + PDO_STMT_OPTION_DIRECT_QUERY, + PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, + PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, + PDO_STMT_OPTION_EMULATE_PREPARES, +}; + +// List of all the statement options supported by this driver. +const stmt_option PDO_STMT_OPTS[] = { + + { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr( new stmt_option_query_timeout ) }, + { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr( new stmt_option_scrollable ) }, + { NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr( new stmt_option_encoding ) }, + { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr( new stmt_option_direct_query ) }, + { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, + { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, + { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, + + { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, +}; + +// boolean connection string +struct pdo_bool_conn_str_func +{ + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); +}; + +struct pdo_txn_isolation_conn_attr_func +{ + static void func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); +}; + +template +struct pdo_int_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); + + size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +template +struct pdo_bool_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( core_str_zval_is_true( value )), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +// statement options related functions +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval** data TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* pdo_stmt_options_ht TSRMLS_DC ); + +} // namespace + + +// List of all connection options supported by this driver. +const connection_option PDO_CONN_OPTS[] = { + { + PDOConnOptionNames::Server, + sizeof( PDOConnOptionNames::Server ), + PDO_CONN_OPTION_SERVER, + NULL, + 0, + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::APP, + sizeof( PDOConnOptionNames::APP ), + SQLSRV_CONN_OPTION_APP, + ODBCConnOptions::APP, + sizeof( ODBCConnOptions::APP ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::ApplicationIntent, + sizeof( PDOConnOptionNames::ApplicationIntent ), + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + ODBCConnOptions::ApplicationIntent, + sizeof( ODBCConnOptions::ApplicationIntent ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::AttachDBFileName, + sizeof( PDOConnOptionNames::AttachDBFileName ), + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + ODBCConnOptions::AttachDBFileName, + sizeof( ODBCConnOptions::AttachDBFileName ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::ConnectionPooling, + sizeof( PDOConnOptionNames::ConnectionPooling ), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof( ODBCConnOptions::ConnectionPooling ), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + PDOConnOptionNames::Database, + sizeof( PDOConnOptionNames::Database ), + SQLSRV_CONN_OPTION_DATABASE, + ODBCConnOptions::Database, + sizeof( ODBCConnOptions::Database ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::Encrypt, + sizeof( PDOConnOptionNames::Encrypt ), + SQLSRV_CONN_OPTION_ENCRYPT, + ODBCConnOptions::Encrypt, + sizeof( ODBCConnOptions::Encrypt ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::Failover_Partner, + sizeof( PDOConnOptionNames::Failover_Partner ), + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + ODBCConnOptions::Failover_Partner, + sizeof( ODBCConnOptions::Failover_Partner ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::LoginTimeout, + sizeof( PDOConnOptionNames::LoginTimeout ), + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + ODBCConnOptions::LoginTimeout, + sizeof( ODBCConnOptions::LoginTimeout ), + CONN_ATTR_INT, + pdo_int_conn_attr_func::func + }, + { + PDOConnOptionNames::MARS_Option, + sizeof( PDOConnOptionNames::MARS_Option ), + SQLSRV_CONN_OPTION_MARS, + ODBCConnOptions::MARS_ODBC, + sizeof( ODBCConnOptions::MARS_ODBC ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::MultiSubnetFailover, + sizeof( PDOConnOptionNames::MultiSubnetFailover ), + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + ODBCConnOptions::MultiSubnetFailover, + sizeof( ODBCConnOptions::MultiSubnetFailover ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::QuotedId, + sizeof( PDOConnOptionNames::QuotedId ), + SQLSRV_CONN_OPTION_QUOTED_ID, + ODBCConnOptions::QuotedId, + sizeof( ODBCConnOptions::QuotedId ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::TraceFile, + sizeof( PDOConnOptionNames::TraceFile ), + SQLSRV_CONN_OPTION_TRACE_FILE, + ODBCConnOptions::TraceFile, + sizeof( ODBCConnOptions::TraceFile ), + CONN_ATTR_STRING, + str_conn_attr_func::func + }, + { + PDOConnOptionNames::TraceOn, + sizeof( PDOConnOptionNames::TraceOn ), + SQLSRV_CONN_OPTION_TRACE_ON, + ODBCConnOptions::TraceOn, + sizeof( ODBCConnOptions::TraceOn ), + CONN_ATTR_BOOL, + pdo_bool_conn_attr_func::func + }, + { + PDOConnOptionNames::TransactionIsolation, + sizeof( PDOConnOptionNames::TransactionIsolation ), + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + ODBCConnOptions::TransactionIsolation, + sizeof( ODBCConnOptions::TransactionIsolation ), + CONN_ATTR_INT, + pdo_txn_isolation_conn_attr_func::func + }, + { + PDOConnOptionNames::TrustServerCertificate, + sizeof( PDOConnOptionNames::TrustServerCertificate ), + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + ODBCConnOptions::TrustServerCertificate, + sizeof( ODBCConnOptions::TrustServerCertificate ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::WSID, + sizeof( PDOConnOptionNames::WSID ), + SQLSRV_CONN_OPTION_WSID, + ODBCConnOptions::WSID, + sizeof( ODBCConnOptions::WSID ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table +}; + + +// close the connection +int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ); + +// execute queries +int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ); + +// transaction support functions +int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ); + +// attribute functions +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ); +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ); + +// return more information +int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, + zval *info TSRMLS_DC); + +// return the last id generated by an executed SQL statement +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, size_t* len TSRMLS_DC ); + +// additional methods are supported in this function +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ); + +// quote a string, meaning put quotes around it and escape any quotes within it +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquotedlen, char **quoted, size_t* quotedlen, + enum pdo_param_type paramtype TSRMLS_DC ); + +struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { + + pdo_sqlsrv_dbh_close, + pdo_sqlsrv_dbh_prepare, + pdo_sqlsrv_dbh_do, + pdo_sqlsrv_dbh_quote, + pdo_sqlsrv_dbh_begin, + pdo_sqlsrv_dbh_commit, + pdo_sqlsrv_dbh_rollback, + pdo_sqlsrv_dbh_set_attr, + pdo_sqlsrv_dbh_last_id, + pdo_sqlsrv_dbh_return_error, + pdo_sqlsrv_dbh_get_attr, + NULL, // check liveness not implemented + pdo_sqlsrv_get_driver_methods, + NULL, // request shutdown not implemented + NULL // in transaction not implemented +}; + + +// log a function entry point +#define PDO_LOG_DBH_ENTRY \ +{ \ + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ + driver_dbh->set_func( __FUNCTION__ ); \ + LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ +} + +// constructor for the internal object for connections +pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : + sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), + stmts( NULL ), + direct_query( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size ) ) +{ + if( client_buffer_max_size < 0 ) { + client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; + LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." ); + } +} + +// pdo_sqlsrv_db_handle_factory +// Maps to PDO::__construct. +// Factory method called by the PDO driver manager to create a SQLSRV PDO connection. +// Does the following things: +// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION. +// (If an error occurs in this function, the PDO specification mandates that +// an exception be thrown, regardless of the error mode setting.) +// 2. Processes the driver options. +// 3. Creates a core_conn object by calling core_sqlsrv_connect. +// 4. Restores the previous error mode on success. +// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory +// Parameters: +// dbh - The PDO managed structure for the connection. +// driver_options - A HashTable (within the zval) of options to use when creating the connection. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) +{ + LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" ); + + hash_auto_ptr pdo_conn_options_ht; + pdo_error_mode prev_err_mode = dbh->error_mode; + + // must be done in all cases so that even a failed connection can query the + // object for errors. + dbh->methods = &pdo_sqlsrv_dbh_methods; + dbh->driver_data = NULL; + zval* temp_server_z = NULL; + sqlsrv_malloc_auto_ptr dsn_parser; + zval server_z; + + try { + + // no matter what the error mode, we want exceptions thrown if the connection fails + // to happen (per the PDO spec) + dbh->error_mode = PDO_ERRMODE_EXCEPTION; + + g_henv_cp->set_driver( dbh ); + g_henv_ncp->set_driver( dbh ); + + CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) { + throw core::CoreException(); + } + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( pdo_conn_options_ht ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, + ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source, + static_cast( dbh->data_source_len ), pdo_conn_options_ht ); + dsn_parser->parse_conn_string( TSRMLS_C ); + + // Extract the server name + temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER); + + CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) { + + throw pdo::PDOException(); + } + + server_z = *temp_server_z; + + // Add a reference to the option value since we are deleting it from the hashtable + zval_add_ref( &server_z ); + zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); + + sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL(server_z ), + dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, + PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); + + // Free the string in server_z after being used + zend_string_release(Z_STR(server_z)); + + SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." ); + + // set the driver_data and methods to complete creation of the PDO object + dbh->driver_data = conn; + dbh->error_mode = prev_err_mode; // reset the error mode + dbh->alloc_own_columns = 1; // we do our own memory management for columns + dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types + + } + catch( core::CoreException& ) { + + dbh->error_mode = prev_err_mode; // reset the error mode + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" ); + } + + return 1; +} + +// pdo_sqlsrv_dbh_close +// Maps to PDO::__destruct. +// Called when a PDO object is to be destroyed. +// By the time this function is called, PDO has already made sure that +// all statements are disposed and the PDO object is the last item destroyed. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// Always returns 1 for success. +int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ) +{ + LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" ); + + // if the connection didn't complete properly, driver_data isn't initialized. + if( dbh->driver_data == NULL ) { + + return 1; + } + + PDO_RESET_DBH_ERROR; + + // call the core layer close + core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) TSRMLS_CC ); + dbh->driver_data = NULL; + + // always return success that the connection is closed + return 1; +} + +// pdo_sqlsrv_dbh_prepare +// Called by PDO::prepare and PDOStatement::__construct. +// Creates a statement and prepares it for execution by PDO +// Paramters: +// dbh - The PDO managed connection object. +// sql - SQL query to be prepared. +// sql_len - Length of the sql query +// stmt - The PDO managed statement object. +// driver_options - User provided list of statement options. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + hash_auto_ptr pdo_stmt_options_ht; + sqlsrv_malloc_auto_ptr sql_rewrite; + size_t sql_rewrite_len = 0; + sqlsrv_malloc_auto_ptr driver_stmt; + + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null"); + + try { + + // assign the methods for the statement object. This is necessary even if the + // statement fails so the user can retrieve the error information. + stmt->methods = &pdo_sqlsrv_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( pdo_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC ); + + driver_stmt = static_cast( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, + pdo_stmt_options_ht, PDO_STMT_OPTS, + pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC )); + + // if the user didn't set anything in the prepare options, then set the buffer limit + // to the value set on the connection. + if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { + + driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size; + } + + // if the user didn't set anything in the prepare options, then set the query timeout + // to the value set on the connection. + if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) { + + core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC ); + } + + // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the + // parameter substitution for us + if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { + + // rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named + // parameters for consistency with the PDO MySQL and PDO ODBC drivers. + int zr = pdo_subst_named_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC ); + CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE ) { + throw core::CoreException(); + } + // if parameter substitution happened, use that query instead of the original + if( sql_rewrite != NULL ) { + sql = sql_rewrite; + sql_len = sql_rewrite_len; + } + } + + if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { + + core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC ); + } + else if( driver_stmt->direct_query ) { + + if( driver_stmt->direct_query_subst_string ) { + // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme + // and we use estrdup below to allocate the new string, which uses emalloc + efree( reinterpret_cast( const_cast( driver_stmt->direct_query_subst_string ))); + } + driver_stmt->direct_query_subst_string = estrdup( sql ); + driver_stmt->direct_query_subst_string_len = sql_len; + } + // else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be + // set to the substituted query + + + stmt->driver_data = driver_stmt; + driver_stmt.transferred(); + } + // everything is cleaned up by this point + // catch everything so the exception doesn't spill into the calling PDO code + catch( core::CoreException& ) { + + if( driver_stmt ) { + + driver_stmt->~pdo_sqlsrv_stmt(); + } + + // in the event that the statement caused an error that was copied to the connection, update the + // connection with the error's SQLSTATE. + if( driver_dbh->last_error() ) { + + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_dbh->last_error()->sqlstate )); + } + + return 0; + } + + // catch any errant exception and die + catch(...) { + + DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." ); + } + + return 1; +} + + +// pdo_sqlsrv_dbh_do +// Maps to PDO::exec. +// Execute a SQL statement, such as an insert, update or delete, and return +// the number of rows affected. +// Parameters: +// dbh - the PDO connection object, which contains the ODBC handle +// sql - the query to execute +// sql_len - length of sql query +// Return +// # of rows affected, -1 for an error. +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + sqlsrv_malloc_auto_ptr driver_stmt; + SQLLEN rows = 0; + + // verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong + // thing to happen here. + SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN )); + + try { + + SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." ); + + // temp PDO statement used for error handling if something happens + pdo_stmt_t temp_stmt; + temp_stmt.dbh = dbh; + // allocate a full driver statement to take advantage of the error handling + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, + NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt->set_func( __FUNCTION__ ); + + core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + + // since the user can give us a compound statement, we return the row count for the last set, and since the row count + // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. + if( core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) { + + SQLRETURN r = SQL_SUCCESS; + + do { + + rows = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + r = core::SQLMoreResults( driver_stmt TSRMLS_CC ); + + } while( r != SQL_NO_DATA ); + } + + // returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases + // naturally, so we override that here with no rows returned. + if( rows == -1 ) { + rows = 0; + } + } + catch( core::CoreException& ) { + + // copy any errors on the statement to the connection so that the user sees them, since the statement is released + // before this method returns + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_stmt->last_error()->sqlstate )); + driver_dbh->set_last_error( driver_stmt->last_error() ); + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + return -1; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." ); + } + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + return rows; +} + + +// transaction support functions + +// pdo_sqlsrv_dbh_begin +// Maps to PDO::beginTransaction. +// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn +// flag is maintained by PDO so we dont have to worry about it. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" ); + + core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred."); + } +} + + + +// pdo_sqlsrv_dbh_commit +// Maps to PDO::commit. +// Commits a transaction. Returns the connection to auto-commit mode. +// PDO throws error if PDO::commit is called on a connection that is not in an active +// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have +// to worry about it here. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" ); + + core_sqlsrv_commit( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred."); + } +} + +// pdo_sqlsrv_dbh_rollback +// Maps to PDO::rollback. +// Rolls back a transaction. Returns the connection in auto-commit mode. +// PDO throws error if PDO::rollBack is called on a connection that is not in an active +// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have +// to worry about it here. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" ); + + core_sqlsrv_rollback( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred."); + } +} + +// pdo_sqlsrv_dbh_set_attr +// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object. +// PDO driver manager calls this function directly after calling the factory +// method for PDO, for any attribute which is specified in the PDO constructor. +// Parameters: +// dbh - The PDO connection object maintained by PDO. +// attr - The attribute to be set. +// val - The value of the attribute to be set. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_ENCODING: + { + zend_long attr_value; + if( Z_TYPE_P( val ) != IS_LONG ) { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + } + attr_value = Z_LVAL_P( val ); + switch( attr_value ) { + + case SQLSRV_ENCODING_DEFAULT: + // when default is applied to a connection, that means use UTF-8 encoding + driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 ); + break; + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_UTF8: + driver_dbh->set_encoding( static_cast( attr_value )); + break; + default: + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + break; + } + } + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) { + convert_to_string( val ); + THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val )); + } + driver_dbh->query_timeout = static_cast( Z_LVAL_P( val ) ); + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) { + convert_to_string( val ); + THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val )); + } + driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); + break; + + // Not supported + case PDO_ATTR_FETCH_TABLE_NAMES: + case PDO_ATTR_FETCH_CATALOG_NAMES: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_MAX_COLUMN_LEN: + case PDO_ATTR_CURSOR_NAME: + case PDO_ATTR_AUTOCOMMIT: + case PDO_ATTR_PERSISTENT: + case PDO_ATTR_TIMEOUT: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); + } + + // Read-only + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_DRIVER_NAME: + case PDO_ATTR_CONNECTION_STATUS: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR ); + } + + // Statement level only + case PDO_ATTR_EMULATE_PREPARES: + case PDO_ATTR_CURSOR: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); + } + + default: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); + break; + } + } + } + catch( pdo::PDOException& ) { + + return 0; + } + + return 1; +} + + +// pdo_sqlsrv_dbh_get_attr +// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object. +// Parameters: +// dbh - The PDO connection object maintained by PDO. +// attr - The attribute to get. +// return_value - zval in which to return the attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + try { + + switch( attr ) { + + // Not supported + case PDO_ATTR_FETCH_TABLE_NAMES: + case PDO_ATTR_FETCH_CATALOG_NAMES: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_MAX_COLUMN_LEN: + case PDO_ATTR_CURSOR_NAME: + case PDO_ATTR_AUTOCOMMIT: + case PDO_ATTR_TIMEOUT: + { + // PDO does not throw "not supported" error message for these attributes. + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); + } + + // Statement level only + case PDO_ATTR_EMULATE_PREPARES: + case PDO_ATTR_CURSOR: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); + } + + case PDO_ATTR_STRINGIFY_FETCHES: + { + // For this attribute, if we dont set the return_value than PDO returns NULL. + ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) ); + break; + } + + case PDO_ATTR_SERVER_INFO: + { + core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC ); + break; + } + + case PDO_ATTR_SERVER_VERSION: + { + core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC ); + break; + } + + case PDO_ATTR_CLIENT_VERSION: + { + core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC ); + + //Add the PDO SQLSRV driver's file version + core::sqlsrv_add_assoc_string( *driver_dbh, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/ + TSRMLS_CC ); + break; + } + + case SQLSRV_ATTR_ENCODING: + { + ZVAL_LONG( return_value, driver_dbh->encoding() ); + break; + } + + case SQLSRV_ATTR_QUERY_TIMEOUT: + { + ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout )); + break; + } + + case SQLSRV_ATTR_DIRECT_QUERY: + { + ZVAL_BOOL( return_value, driver_dbh->direct_query ); + break; + } + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + { + ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size ); + break; + } + + default: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); + break; + } + } + + return 1; + } + catch( core::CoreException& ) { + return 0; + } +} + +// Called by PDO::errorInfo and PDOStatement::errorInfo. +// Returns the error info. +// Parameters: +// dbh - The PDO managed connection object. +// stmt - The PDO managed statement object. +// info - zval in which to return the error info. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, + zval *info TSRMLS_DC) +{ + SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." ); + + sqlsrv_error* ctx_error = NULL; + if( stmt ) { + ctx_error = static_cast( stmt->driver_data )->last_error(); + } + else { + ctx_error = static_cast( dbh->driver_data )->last_error(); + } + + pdo_sqlsrv_retrieve_context_error( ctx_error, info ); + + return 1; +} + +// pdo_sqlsrv_dbh_last_id +// Maps to PDO::lastInsertId. +// Returns the last id generated by an executed SQL statement +// Parameters: +// dbh - The PDO managed connection object. +// name - Table name. +// len - Length of the name. +// Return: +// Returns the last insert id as a string. +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out size_t* len TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + // turn off any error handling for last_id + pdo_error_mode prev_err_mode = dbh->error_mode; + dbh->error_mode = PDO_ERRMODE_SILENT; + + sqlsrv_malloc_auto_ptr driver_stmt; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + sqlsrv_malloc_auto_ptr id_str; + id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); + + try { + + char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; + if( name == NULL ) { + strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); + } + else { + char* quoted_table = NULL; + size_t quoted_len = 0; + int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); + SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name." ); + sprintf_s( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table ); + sqlsrv_free( quoted_table ); + } + + // temp PDO statement used for error handling if something happens + pdo_stmt_t temp_stmt; + temp_stmt.dbh = dbh; + + // allocate a full driver statement to take advantage of the error handling + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, + NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt->set_func( __FUNCTION__ ); + + // execute the last insert id query + core::SQLExecDirect( driver_stmt, last_insert_id_query TSRMLS_CC ); + + core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, + reinterpret_cast( len ), false TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, + PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { + throw core::CoreException(); + } + + driver_stmt->~sqlsrv_stmt(); + } + catch( core::CoreException& ) { + + // copy any errors on the statement to the connection so that the user sees them, since the statement is released + // before this method returns + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_stmt->last_error()->sqlstate )); + driver_dbh->set_last_error( driver_stmt->last_error() ); + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + strcpy_s( id_str.get(), 1, "" ); + *len = 0; + } + + char* ret_id_str = id_str.get(); + id_str.transferred(); + + // restore error handling to its previous mode + dbh->error_mode = prev_err_mode; + + return ret_id_str; +} + +// pdo_sqlsrv_dbh_quote +// Maps to PDO::quote. As the name says, this function quotes a string. +// Always returns a valid string unless memory allocation fails. +// Parameters: +// dbh - The PDO managed connection object. +// unquoted - The unquoted string to be quoted. +// unquoted_len - Length of the unquoted string. +// quoted - Buffer for output string. +// quoted_len - Length of the output string. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, + enum pdo_param_type /*paramtype*/ TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + // count the number of quotes needed + unsigned int quotes_needed = 2; // the initial start and end quotes of course + for(size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + if( unquoted[ index ] == '\'' ) { + ++quotes_needed; + } + } + + *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. + unsigned int out_current = 0; + + // insert initial quote + (*quoted)[ out_current++ ] ='\''; + + for(size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + + if( unquoted[ index ] == '\'' ) { + (*quoted)[ out_current++ ] = '\''; + (*quoted)[ out_current++ ] = '\''; + } + else { + (*quoted)[ out_current++ ] = unquoted[ index ]; + } + } + + // trailing quote and null terminator + (*quoted)[ out_current++ ] ='\''; + (*quoted)[ out_current ] = '\0'; + + return 1; +} + +// This method is not implemented by this driver. +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) { + return NULL; + } + + return NULL; // to avoid a compiler warning +} + +namespace { + +// Maps the PDO driver specific statement option/attribute constants to the core layer +// statement option/attribute constants. +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval* data TSRMLS_DC ) +{ + zend_ulong option_key = -1; + switch( key ) { + + case PDO_ATTR_CURSOR: + option_key = SQLSRV_STMT_OPTION_SCROLLABLE; + break; + + case SQLSRV_ATTR_ENCODING: + option_key = PDO_STMT_OPTION_ENCODING; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; + break; + + case PDO_ATTR_STATEMENT_CLASS: + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + option_key = PDO_STMT_OPTION_DIRECT_QUERY; + break; + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; + break; + + case PDO_ATTR_EMULATE_PREPARES: + option_key = PDO_STMT_OPTION_EMULATE_PREPARES; + break; + + default: + CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } + break; + } + + // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it + if( option_key != -1 ) { + zval_add_ref( data ); + core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); + } +} + + +// validate_stmt_options +// Iterates through the list of statement options provided by the user and validates them +// against the list of statement options provided by this driver. After validation +// creates a Hashtable of statement options to be sent to the core layer for processing. +// Parameters: +// ctx - The current context. +// stmt_options - The user provided list of statement options. +// pdo_stmt_options_ht - Output hashtable of statement options. +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* pdo_stmt_options_ht TSRMLS_DC ) +{ + try { + + if( stmt_options ) { + + HashTable* options_ht = Z_ARRVAL_P( stmt_options ); + + for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS; + zend_hash_move_forward( options_ht )) { + + int type = HASH_KEY_NON_EXISTENT; + zend_string *key = NULL; + size_t int_key = -1; + zval* data; + int result = 0; + + type = zend_hash_get_current_key( options_ht, &key, &int_key); + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } + + core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, data TSRMLS_CC ); + + add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); + } + } + } + catch( core::CoreException& ) { + + throw; + } +} + + +void pdo_bool_conn_str_func::func(connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) +{ + TSRMLS_C; + char const* val_str = "no"; + + if( core_str_zval_is_true( value ) ) { + + val_str = "yes"; + } + + conn_str += option->odbc_name; + conn_str += "={"; + conn_str += val_str; + conn_str += "};"; +} + +void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, + std::string& /*conn_str*/ TSRMLS_DC ) +{ + try { + + SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." ); + const char* val = Z_STRVAL_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); + zend_long out_val = SQL_TXN_READ_COMMITTED; + + // READ_COMMITTED + if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 ) + && !_stricmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) { + + out_val = SQL_TXN_READ_COMMITTED; + } + + // READ_UNCOMMITTED + else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 ) + && !_stricmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) { + + out_val = SQL_TXN_READ_UNCOMMITTED; + } + + // REPEATABLE_READ + else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 ) + && !_stricmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) { + + out_val = SQL_TXN_REPEATABLE_READ; + } + + // SERIALIZABLE + else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 ) + && !_stricmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) { + + out_val = SQL_TXN_SERIALIZABLE; + } + + // SNAPSHOT + else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 ) + && !_stricmp( val, PDOTxnIsolationValues::SNAPSHOT ))) { + + out_val = SQL_TXN_SS_SNAPSHOT; + } + + else { + + CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) { + + throw core::CoreException(); + } + } + + core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch( core::CoreException& ) { + + throw; + } +} + +} // namespace diff --git a/pdo_sqlsrv/pdo_init.cpp b/pdo_sqlsrv/pdo_init.cpp new file mode 100644 index 00000000..9e6717bc --- /dev/null +++ b/pdo_sqlsrv/pdo_init.cpp @@ -0,0 +1,402 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_init.cpp +// +// Contents: initialization routines for PDO_SQLSRV +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "pdo_sqlsrv.h" +#include + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(g_pdo_sqlsrv) + +extern "C" { + +ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv); + +} + +// module global variables (initialized in minit and freed in mshutdown) +HashTable* g_pdo_errors_ht = NULL; + +// henv context for creating connections +sqlsrv_context* g_henv_cp; +sqlsrv_context* g_henv_ncp; + +namespace { + +pdo_driver_t pdo_sqlsrv_driver = { + + PDO_DRIVER_HEADER(sqlsrv), + pdo_sqlsrv_db_handle_factory +}; + +const char PDO_DLL_NAME[] = "php_pdo.dll"; + +// PHP_DEBUG is always defined as either 0 or 1 +#if PHP_DEBUG == 1 && !defined(ZTS) + +const char PHP_DLL_NAME[] = "php7_debug.dll"; + +#elif PHP_DEBUG == 1 && defined(ZTS) + +const char PHP_DLL_NAME[] = "php7ts_debug.dll"; + +#elif PHP_DEBUG == 0 && !defined(ZTS) + +const char PHP_DLL_NAME[] = "php7.dll"; + +#elif PHP_DEBUG == 0 && defined(ZTS) + +const char PHP_DLL_NAME[] = "php7ts.dll"; + +#else + +#error Invalid combination of PHP_DEBUG and ZTS macros + +#endif + +typedef PDO_API int (*pdo_register_func)(pdo_driver_t *); + +// function pointers to register and unregister this driver +pdo_register_func pdo_register_driver; +pdo_register_func pdo_unregister_driver; + +// functions to register SQLSRV constants with the PDO class +// (It's in all CAPS so it looks like the Zend macros that do similar work) +void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ); +void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ); + +// return the Zend class entry for the PDO dbh (connection) class +zend_class_entry* (*pdo_get_dbh_class)( void ); + +struct sqlsrv_attr_pdo_constant { + const char *name; + int value; +}; + +// forward decl for table +extern sqlsrv_attr_pdo_constant pdo_attr_constants[]; + +} + +static zend_module_dep pdo_sqlsrv_depends[] = { + ZEND_MOD_REQUIRED("pdo") + {NULL, NULL, NULL} +}; + + +// argument info structures for functions, arranged alphabetically. +// see zend_API.h in the PHP sources for more information about these macros + +// function table with associated arginfo structures +zend_function_entry pdo_sqlsrv_functions[] = { + {NULL, NULL, NULL} // no functions directly defined by this driver +}; + +// 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_pdo_sqlsrv_module_entry = +{ + STANDARD_MODULE_HEADER_EX, + NULL, + pdo_sqlsrv_depends, + "pdo_sqlsrv", + pdo_sqlsrv_functions, // exported function table + // initialization and shutdown functions + PHP_MINIT(pdo_sqlsrv), + PHP_MSHUTDOWN(pdo_sqlsrv), + PHP_RINIT(pdo_sqlsrv), + PHP_RSHUTDOWN(pdo_sqlsrv), + PHP_MINFO(pdo_sqlsrv), + // version of the extension. Matches the version resource of the extension dll + VER_FILEVERSION_STR, + PHP_MODULE_GLOBALS(pdo_sqlsrv), + NULL, + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +// functions dynamically linked from the PDO (or PHP) dll and called by other parts of the driver +zend_class_entry* (*pdo_get_exception_class)( void ); +int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, + char **outquery, size_t *outquery_len TSRMLS_DC); + +// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed +void pdo_error_dtor(zval* elem) { + pdo_error* error_to_ignore = reinterpret_cast(Z_PTR_P(elem)); + pefree(error_to_ignore, 1); +} + +// Module initialization +// This function is called once per execution of the Zend engine + +PHP_MINIT_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( type ); + + // our global variables are initialized in the RINIT function +#if defined(ZTS) + if( ts_allocate_id( &pdo_sqlsrv_globals_id, + sizeof( zend_pdo_sqlsrv_globals ), + (ts_allocate_ctor) NULL, + (ts_allocate_dtor) NULL ) == 0 ) + return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + core_sqlsrv_register_logger( pdo_sqlsrv_log ); + + REGISTER_INI_ENTRIES(); + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" ); + + // PHP extensions may be either external DLLs loaded by PHP or statically compiled into the PHP dll + // This becomes an issue when we are dependent on other extensions, e.g. PDO. Normally this is solved + // by the build process by linking our extension to the appropriate import library (either php*.dll or php_pdo.dll) + // However, this leaves us with a problem that the extension has a dependency on that build type. + // Since we don't distribute our extension with PHP directly (yet), it would mean that we would have to have SKUs + // for both types of PDO builds, internal and external. Rather than this, we just dynamically link the PDO routines we call + // against either the PDO dll if it exists and is loaded, otherwise against the PHP dll directly. + + DWORD needed = 0; + HANDLE hprocess = GetCurrentProcess(); + HMODULE pdo_hmodule; + + pdo_hmodule = GetModuleHandle( PDO_DLL_NAME ); + if( pdo_hmodule == 0 ) { + + pdo_hmodule = GetModuleHandle( PHP_DLL_NAME ); + if( pdo_hmodule == NULL ) { + LOG( SEV_ERROR, "Failed to get PHP module handle." ); + return FAILURE; + } + } + + pdo_register_driver = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_register_driver" )); + if( pdo_register_driver == NULL ) { + LOG( SEV_ERROR, "Failed to register driver." ); + return FAILURE; + } + + pdo_unregister_driver = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_unregister_driver" )); + if( pdo_unregister_driver == NULL ) { + LOG( SEV_ERROR, "Failed to register driver." ); + return FAILURE; + } + + pdo_get_exception_class = reinterpret_cast( GetProcAddress( pdo_hmodule, + "php_pdo_get_exception" )); + if( pdo_get_exception_class == NULL ) { + LOG( SEV_ERROR, "Failed to register driver." ); + return FAILURE; + } + + pdo_get_dbh_class = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_get_dbh_ce" )); + if( pdo_get_dbh_class == NULL ) { + LOG( SEV_ERROR, "Failed to register driver." ); + return FAILURE; + } + + pdo_subst_named_params = + reinterpret_cast( + GetProcAddress( pdo_hmodule, "pdo_parse_params" )); + if( pdo_subst_named_params == NULL ) { + LOG( SEV_ERROR, "Failed to register driver." ); + return FAILURE; + } + + // initialize list of pdo errors + g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + ::zend_hash_init( g_pdo_errors_ht, 50, NULL, pdo_error_dtor /*pDestructor*/, 1 ); + + for( int i = 0; PDO_ERRORS[ i ].error_code != -1; ++i ) { + + void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, + &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); + if( zr == NULL ) { + + LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); + return FAILURE; + } + } + + try { + + // register all attributes supported by this driver. + for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) { + + REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC ); + + } + + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC ); + + // retrieve the handles for the environments + core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC ); + + } + catch( ... ) { + + return FAILURE; + } + + pdo_register_driver( &pdo_sqlsrv_driver ); + + return SUCCESS; +} + +// Module shutdown function + +// Module shutdown function +// This function is called once per execution of the Zend engine + +PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv) +{ + try { + + SQLSRV_UNUSED( type ); + + UNREGISTER_INI_ENTRIES(); + + pdo_unregister_driver( &pdo_sqlsrv_driver ); + + // clean up the list of pdo errors + zend_hash_destroy( g_pdo_errors_ht ); + pefree( g_pdo_errors_ht, 1 /*persistent*/ ); + + core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); + + } + catch( ... ) { + + LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" ); + return FAILURE; + } + + return SUCCESS; +} + + +// Request initialization function +// This function is called once per PHP script execution + +PHP_RINIT_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); + + return SUCCESS; +} + + +// Request shutdown +// Called at the end of a script's execution + +PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); + + return SUCCESS; +} + +// Called for php_info(); +// Displays the INI settings registered and their current values + +PHP_MINFO_FUNCTION(pdo_sqlsrv) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "pdo_sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} + +// *** internal init functions *** + +namespace { + + // mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because + // we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT) + + void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ) + { + zend_class_entry* zend_class = pdo_get_dbh_class(); + SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: pdo_get_dbh_class failed" ); + int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value TSRMLS_CC ); + if( zr == FAILURE ) { + throw core::CoreException(); + } + } + + void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ) + { + zend_class_entry* zend_class = pdo_get_dbh_class(); + SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: pdo_get_dbh_class failed" ); + int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) TSRMLS_CC ); + if( zr == FAILURE ) { + + throw core::CoreException(); + } + } + + // array of pdo constants. + sqlsrv_attr_pdo_constant pdo_attr_constants[] = { + + // driver specific attributes + { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, + { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, + { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, + { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, + { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + + // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, + // PDO::PARAM_STR uses the size of the string in the variable + { "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 }, + + // encoding attributes + { "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT }, + { "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM }, + { "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY }, + { "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 }, + + // cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE + { "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC }, + { "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC }, + { "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN }, + { "SQLSRV_CURSOR_BUFFERED" , static_cast(SQLSRV_CURSOR_BUFFERED) }, + + { NULL , 0 } // terminate the table + }; +} diff --git a/pdo_sqlsrv/pdo_parser.cpp b/pdo_sqlsrv/pdo_parser.cpp new file mode 100644 index 00000000..ffffce56 --- /dev/null +++ b/pdo_sqlsrv/pdo_parser.cpp @@ -0,0 +1,359 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_parser.cpp +// +// Contents: Implements a parser to parse the PDO DSN. +// +// Copyright Microsoft Corporation +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "pdo_sqlsrv.h" + +// Constructor +conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, __inout HashTable* conn_options_ht ) +{ + this->conn_str = dsn; + this->len = len; + this->conn_options_ht = conn_options_ht; + this->pos = -1; + this->ctx = &ctx; +} + +// Move to the next character +inline bool conn_string_parser::next( void ) +{ + // if already at the end then return false + if( this->is_eos() ) { + + return false; + } + + SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" ); + + this->pos++; + + if ( this->is_eos() ) { + + return false; + } + + return true; +} + +// Check for end of string. +inline bool conn_string_parser::is_eos( void ) +{ + if( this->pos == len ) + { + return true; // EOS + } + + SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" ); + + return false; +} + +// Check for white space. +inline bool conn_string_parser::is_white_space( char c ) +{ + if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) { + return true; + } + return false; +} + +// Discard any trailing white spaces. +int conn_string_parser::discard_trailing_white_spaces( const char* str, int len ) +{ + const char* end = str + ( len - 1 ); + + while(( this->is_white_space( *end ) ) && (len > 0) ) { + + len--; + end--; + } + + return len; +} + +// Discard white spaces. +bool conn_string_parser::discard_white_spaces() +{ + if( this->is_eos() ) { + + return false; + } + + while( this->is_white_space( this->conn_str[ pos ] )) { + + if( !next() ) + return false; + } + + return true; +} + +// Add a key-value pair to the hashtable of connection options. +void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) +{ + zval value_z; + ZVAL_UNDEF( &value_z ); + + if( len == 0 ) { + + ZVAL_STRINGL( &value_z, "", 0); + } + else { + + ZVAL_STRINGL( &value_z, const_cast( value ), len ); + } + + core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC ); +} + +// Validate a given DSN keyword. +void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) +{ + int new_len = discard_trailing_white_spaces( key, key_len ); + + for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + { + // discard the null terminator. + if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !_strnicmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { + + this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; + this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; + return; + } + } + + // encountered an invalid key, throw error. + sqlsrv_malloc_auto_ptr key_name; + key_name = static_cast( sqlsrv_malloc( new_len + 1 )); + memcpy( key_name, key, new_len ); + key_name[ new_len ] = '\0'; + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, key_name ); +} + +// Primary function which parses the connection string/DSN. +void conn_string_parser:: parse_conn_string( TSRMLS_D ) +{ + States state = FirstKeyValuePair; // starting state + int start_pos = -1; + + try { + + while( !this->is_eos() ) { + + switch( state ) { + + case FirstKeyValuePair: + { + // discard leading spaces + if( !next() || !discard_white_spaces() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS + } + + state = Key; + break; + } + + case Key: + { + start_pos = this->pos; + + // read the key name + while( this->conn_str[ pos ] != '=' ) { + + if( !next() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS + } + } + + this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); + + state = Value; + + break; + } + + case Value: + { + SQLSRV_ASSERT(( this->conn_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: " + "Equal was expected" ); + + next(); // skip "=" + + // if EOS encountered after 0 or more spaces OR semi-colon encountered. + if( !discard_white_spaces() || this->conn_str[ pos ] == ';' ) { + + add_key_value_pair( NULL, 0 TSRMLS_CC ); + + if( this->is_eos() ) { + + break; // EOS + } + else { + + // this->conn_str[ pos ] == ';' + state = NextKeyValuePair; + } + } + + // if LCB + else if( this->conn_str[ pos ] == '{' ) { + + start_pos = this->pos; // starting character is LCB + state = ValueContent1; + } + + // If NonSP-LCB-SC + else { + + start_pos = this->pos; + state = ValueContent2; + } + + break; + } + + case ValueContent1: + { + while ( this->conn_str[ pos ] != '}' ) { + + if ( ! next() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name ); + } + } + + // If we reached here than RCB encountered + state = RCBEncountered; + + break; + } + + case ValueContent2: + { + while( this->conn_str[ pos ] != ';' ) { + + if( ! next() ) { + + break; //EOS + } + } + + if( !this->is_eos() && this->conn_str[ pos ] == ';' ) { + + // semi-colon encountered, so go to next key-value pair + state = NextKeyValuePair; + } + + add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + + SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), + "conn_string_parser::parse_conn_string: Invalid state encountered " ); + + break; + } + + case RCBEncountered: + { + + // Read the next character after RCB. + if( !next() ) { + + // EOS + add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + break; + } + + SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" ); + + // if second RCB encountered than go back to ValueContent1 + if( this->conn_str[ pos ] == '}' ) { + + if( !next() ) { + + // EOS after a second RCB is error + THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name ); + } + + state = ValueContent1; + break; + } + + int end_pos = this->pos; + + // discard any trailing white-spaces. + if( this->is_white_space( this->conn_str[ pos ] )) { + + if( ! this->discard_white_spaces() ) { + + //EOS + add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + break; + } + } + + // if semi-colon than go to next key-value pair + if ( this->conn_str[ pos ] == ';' ) { + + add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + state = NextKeyValuePair; + break; + } + + // Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error. + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name ); + break; + } + case NextKeyValuePair: + { + SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ), + "conn_string_parser::parse_conn_string: semi-colon was expected." ); + + // Call next() to skip the semi-colon. + if( !next() || !this->discard_white_spaces() ) { + + // EOS + break; + } + + if( this->conn_str[ pos ] == ';' ) { + + // a second semi-colon is error case. + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); + } + + else { + + // any other character leads to the next key + state = Key; + break; + } + } //case NextKeyValuePair + } // switch + } //while + } + catch( pdo::PDOException& ) { + + throw; + } +} + diff --git a/pdo_sqlsrv/pdo_sqlsrv.h b/pdo_sqlsrv/pdo_sqlsrv.h new file mode 100644 index 00000000..3f065cbe --- /dev/null +++ b/pdo_sqlsrv/pdo_sqlsrv.h @@ -0,0 +1,393 @@ +#ifndef PDO_SQLSRV_H +#define PDO_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_sqlsrv.h +// +// Contents: Declarations for the extension +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +extern "C" { + +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "pdo/php_pdo_int.h" + +} + +#include +#include + + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// sqlsrv driver specific PDO attributes +enum PDO_SQLSRV_ATTR { + + // Currently there are only three custom attributes for this driver. + SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, + SQLSRV_ATTR_QUERY_TIMEOUT, + SQLSRV_ATTR_DIRECT_QUERY, + SQLSRV_ATTR_CURSOR_SCROLL_TYPE, + SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, +}; + +// valid set of values for TransactionIsolation connection option +namespace PDOTxnIsolationValues { + + const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; + const char READ_COMMITTED[] = "READ_COMMITTED"; + const char REPEATABLE_READ[] = "REPEATABLE_READ"; + const char SERIALIZABLE[] = "SERIALIZABLE"; + const char SNAPSHOT[] = "SNAPSHOT"; +} + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +extern "C" { + +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) + +unsigned int log_severity; +zend_long client_buffer_max_size; + +ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) + +ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); + +} + +// macros used to access the global variables. Use these to make global variable access agnostic to threads +#ifdef ZTS +#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) +#else +#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" +#define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PREFIX "pdo_sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, + zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, + client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) +PHP_INI_END() + +// henv context for creating connections +extern sqlsrv_context* g_henv_cp; +extern sqlsrv_context* g_henv_ncp; + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// module global variables (initialized in minit and freed in mshutdown) +extern HashTable* g_pdo_errors_ht; + +// module initialization +PHP_MINIT_FUNCTION(pdo_sqlsrv); +// module shutdown function +PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv); +// request initialization function +PHP_RINIT_FUNCTION(pdo_sqlsrv); +// request shutdown function +PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv); +// module info function (info returned by phpinfo()) +PHP_MINFO_FUNCTION(pdo_sqlsrv); + +extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP + +//********************************************************************************************************************************* +// PDO DSN Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN connection string. +class conn_string_parser +{ + enum States + { + FirstKeyValuePair, + Key, + Value, + ValueContent1, + ValueContent2, + RCBEncountered, + NextKeyValuePair, + }; + + private: + const char* conn_str; + sqlsrv_context* ctx; + int len; + int pos; + unsigned int current_key; + const char* current_key_name; + HashTable* conn_options_ht; + inline bool next( void ); + inline bool is_eos( void ); + inline bool is_white_space( char c ); + bool discard_white_spaces( void ); + int discard_trailing_white_spaces( const char* str, int len ); + void conn_string_parser::validate_key( const char *key, int key_len TSRMLS_DC ); + void add_key_value_pair( const char* value, int len TSRMLS_DC ); + + public: + conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, __inout HashTable* conn_options_ht ); + void parse_conn_string( TSRMLS_D ); +}; + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* +extern const connection_option PDO_CONN_OPTS[]; + +int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC); + +// a core layer pdo dbh object. This object inherits and overrides the statement factory +struct pdo_sqlsrv_dbh : public sqlsrv_conn { + + zval* stmts; + bool direct_query; + long query_timeout; + zend_long client_buffer_max_size; + + pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_encoding : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_scrollable : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_direct_query : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_cursor_scroll_type : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_emulate_prepares : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; + +// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary +struct pdo_sqlsrv_stmt : public sqlsrv_stmt { + + pdo_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + direct_query( false ), + direct_query_subst_string( NULL ), + direct_query_subst_string_len( 0 ), + bound_column_param_types( NULL ) + { + pdo_sqlsrv_dbh* db = static_cast( c ); + direct_query = db->direct_query; + } + + virtual ~pdo_sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + + bool direct_query; // flag set if the query should be executed directly or prepared + const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters + size_t direct_query_subst_string_len; // length of query string used for direct queries + + // meta data for current result set + std::vector > current_meta_data; + pdo_param_type* bound_column_param_types; +}; + + +//********************************************************************************************************************************* +// Error Handling Functions +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct pdo_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// called when an error occurs in the core layer. These routines are set as the error_callback in a +// context. The context is passed to this function since it contains the function + +bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); +bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); +bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); + +// pointer to the function to return the class entry for the PDO exception Set in MINIT +extern zend_class_entry* (*pdo_get_exception_class)( void ); + +// common routine to transfer a sqlsrv_context's error to a PDO zval +void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ); + +// reset the errors from the last operation +inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC ) +{ + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error + + // release the last statement from the dbh so that error handling won't have a statement passed to it + if( dbh->query_stmt ) { + dbh->query_stmt = NULL; + zend_objects_store_del( Z_OBJ_P(&dbh->query_stmt_zval TSRMLS_CC) ); + } + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( dbh->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( dbh->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); + +inline void pdo_reset_stmt_error( pdo_stmt_t* stmt ) +{ + strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( stmt->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( stmt->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); + +// validate the driver objects +#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } +#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } + + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// List of PDO specific error messages. +enum PDO_ERROR_CODES { + + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + PDO_SQLSRV_ERROR_INVALID_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + PDO_SQLSRV_ERROR_PARAM_PARSE, + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + +}; + +extern pdo_error PDO_ERRORS[]; + +#define THROW_PDO_ERROR( ctx, custom, ... ) \ + call_error_handler( ctx, custom TSRMLS_CC, false, __VA_ARGS__ ); \ + throw pdo::PDOException(); + +namespace pdo { + + // an error which occurred in our PDO driver, NOT an exception thrown by PDO + struct PDOException : public core::CoreException { + + PDOException() : CoreException() + { + } + }; + +} // namespace pdo + +// called pdo_parse_params in php_pdo_driver.h +// we renamed it for 2 reasons: 1) we can't have the same name since it would conflict with our dynamic linking, and +// 2) this is a more precise name +extern int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, + char **outquery, size_t *outquery_len TSRMLS_DC); + +// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro +void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +#endif /* PDO_SQLSRV_H */ + diff --git a/pdo_sqlsrv/pdo_stmt.cpp b/pdo_sqlsrv/pdo_stmt.cpp new file mode 100644 index 00000000..20a35936 --- /dev/null +++ b/pdo_sqlsrv/pdo_stmt.cpp @@ -0,0 +1,1297 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_stmt.cpp +// +// Contents: Implements the PDOStatement object for the PDO_SQLSRV +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "pdo_sqlsrv.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 (enum pdo_fetch_orientation ori) +{ + SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range." ); + OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); + OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" ); + 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( sqlsrv_stmt* driver_stmt, enum pdo_param_type pdo_type TSRMLS_DC ) +{ + 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: + // 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( 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( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + 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 ); + SQLULEN 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 TSRMLS_CC ); +} + +void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + 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 ); + } + + SQLULEN odbc_cursor_type = Z_LVAL_P( value_z ); + + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); + + return; +} + +// Sets the statement encoding. Default encoding on the statement +// implies use the connection's encoding. +void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + // 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( attr_value )); + break; + + default: + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + break; + } +} + +// internal helper function to free meta data structures allocated +void meta_data_free( field_meta_data* meta ) +{ + sqlsrv_free( meta ); +} + +zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) +{ + zval 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( in_val ))); + } + else { + ZVAL_DOUBLE( &out_zval, **( reinterpret_cast( 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( *in_val ), field_len ); + sqlsrv_free( *in_val ); + } + break; + } + + case SQLSRV_PHPTYPE_DATETIME: + DIE( "Unsupported php type" ); + out_zval = *( reinterpret_cast( *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(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, + zend_long offset TSRMLS_DC); +int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC); +int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, + char **ptr, size_t *len, int *caller_frees TSRMLS_DC); +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC); +int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC); + +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_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_cursors( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_encoding:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_encoding( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_direct_query:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false; +} + +void stmt_option_cursor_scroll_type:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_stmt_t *pdo_stmt = static_cast( stmt->driver() ); + pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; +} + + +// log a function entry point +#define PDO_LOG_STMT_ENTRY \ +{ \ + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ + driver_stmt->set_func( __FUNCTION__ ); \ + LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ +} + +// PDO SQLSRV statement destructor +pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) +{ + std::for_each( current_meta_data.begin(), current_meta_data.end(), meta_data_free ); + current_meta_data.clear(); + + 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( const_cast( 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(pdo_stmt_t *stmt TSRMLS_DC) +{ + 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" ); + + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: 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. + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); + } + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding 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(pdo_stmt_t *stmt, int colno TSRMLS_DC) +{ + 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_malloc_auto_ptr core_meta_data; + + try { + + core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno TSRMLS_CC ); + } + + 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 ); + core_meta_data->field_name.transferred(); + + // 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; + + // Set the param_type + column_data->param_type = PDO_PARAM_ZVAL; + + // store the field data for use by pdo_sqlsrv_stmt_get_col_data + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( 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(pdo_stmt_t *stmt TSRMLS_DC) +{ + sqlsrv_stmt* driver_stmt = reinterpret_cast( 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; + } + + 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(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( 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->executed && !driver_stmt->past_next_result_end ) { + + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC, 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( 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 ) { + + query = stmt->active_query_string; + query_len = static_cast( stmt->active_query_stringlen ); + } + + core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); + + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + // 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(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, + zend_long offset TSRMLS_DC) +{ + 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( 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 ) { + driver_stmt->bound_column_param_types = + reinterpret_cast( 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 ); + } + + for( long i = 0; i < stmt->column_count; ++i ) { + + if (NULL== (bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, i))) && + (NULL == (bind_data = reinterpret_cast(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; + } + } + } + + SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); + bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); + + // 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 + if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + // 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."); + } +} + +// 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. +int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, + char **ptr, size_t *len, int *caller_frees TSRMLS_DC) +{ + 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( 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; + } + + // Let PDO free the memory after use. + *caller_frees = 1; + + // set the metadata for the current column + pdo_column_data* column_data = &(stmt->columns[colno]); + + // translate the pdo type to a type the core layer understands + sqlsrv_phptype sqlsrv_php_type; + SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), + "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); + sqlsrv_php_type = driver_stmt->sql_type_to_php_type(static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), + static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), true ); + + // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding + sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); + + // 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( stmt->bound_columns && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_ZVAL ) { + + sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, + driver_stmt->bound_column_param_types[ colno ] + TSRMLS_CC ); + + pdo_bound_param_data* bind_data = NULL; + bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); + + 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; + } + } + } + + SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; + core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, reinterpret_cast( ptr ), + reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); + zval* zval_ptr = reinterpret_cast( sqlsrv_malloc( sizeof( zval ))); + *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); + *ptr = reinterpret_cast( zval_ptr ); + + *len = sizeof( zval ); + + return 1; + } + + catch ( core::CoreException& ) { + return 0; + } + catch ( ... ) { + DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred."); + } +} + +// 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(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + + 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 TSRMLS_CC ); + 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 TSRMLS_CC ); + 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 TSRMLS_CC ); + 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( pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC ) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + pdo_sqlsrv_stmt* driver_stmt = static_cast( 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; + } + + 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: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); + + sqlsrv_malloc_auto_ptr core_meta_data; + + sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + + SQLSRV_ASSERT( colno >= 0 && colno < SHRT_MAX, "pdo_sqlsrv_stmt_get_col_meta: tried to overflow a short" ); + core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + // initialize the array to nothing, as PDO requires us to create it + core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + + // add the following fields: flags, native_type, driver:decl_type, table + add_assoc_long( return_value, "flags", 0 ); + + // get the name of the data type + char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + 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, ¬_used TSRMLS_CC ); + 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: + add_assoc_string( return_value, "native_type", "string" ); + 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 ]; + 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 TSRMLS_CC ); + add_assoc_string( return_value, "table", table_name ); + + if( stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) { + add_assoc_long( return_value, "pdo_type", pdo_type ); + } + + // this will ensure that the field_name field, which is an auto pointer gets freed. + (*core_meta_data).~field_meta_data(); + } + catch( core::CoreException& ) { + + return 0; + } + catch(...) { + + DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); + } + + return 1; +} + + +// 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(pdo_stmt_t *stmt TSRMLS_DC) +{ + 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( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); + + core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); + + // clear the current meta data since the new result will generate new meta data + std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free ); + driver_stmt->current_meta_data.clear(); + + // 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 TSRMLS_CC ); + + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + } + 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(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC) +{ + 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: + 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( 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 TSRMLS_CC, 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( 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; + } + else { + direction = SQL_PARAM_OUTPUT; + } + } + // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant + // and the SQLSRV_PHPTYPE_* constant + int pdo_type = param->param_type; + SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; + switch( pdo_type & ~PDO_PARAM_INPUT_OUTPUT ) { + 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( ¶m->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: + 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(); + } + // if the user provided an encoding, use it instead + 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( 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( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, + sql_type, column_size, decimal_digits TSRMLS_CC ); + } + 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; + + // skip column bindings + if( !param->is_param ) { + break; + } + + core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, + &(param->parameter) TSRMLS_CC ); + } + 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( SQLINTEGER sql_type, SQLUINTEGER size, 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." ); + } + + switch( sql_type ) { + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_FLOAT: + case SQL_REAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_WCHAR: + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + 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; +} + diff --git a/pdo_sqlsrv/pdo_util.cpp b/pdo_sqlsrv/pdo_util.cpp new file mode 100644 index 00000000..f56d28ad --- /dev/null +++ b/pdo_sqlsrv/pdo_util.cpp @@ -0,0 +1,609 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_util.cpp +// +// Contents: Utility functions used by both connection or statement functions +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "pdo_sqlsrv.h" + +#include "zend_exceptions.h" + + +// *** internal constants *** +namespace { + +const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nMError Message: %3!s!\n"; +const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s"; +char EXCEPTION_PROPERTY_MSG[] = "message"; +char EXCEPTION_PROPERTY_CODE[] = "code"; +char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo"; +const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative + +// 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 +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; + +// build the object and throw the PDO exception +void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ); + +} + +// pdo driver error messages +// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0 +pdo_error PDO_ERRORS[] = { + + { + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 for SQL Server to " + "communicate with SQL Server. Access the following URL to download the ODBC Driver 11 for SQL Server " + "for %1!s!: " + "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true } + }, + { + SQLSRV_ERROR_ZEND_HASH, + { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams " + "cannot be specified as output parameters.", -3, false } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point " + "numbers, strings, and streams may be used as parameters.", -4, true } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true } + }, + { + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true } + }, + { + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true } + }, + { + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true } + }, + { + SQLSRV_ERROR_ZEND_STREAM, + { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false } + }, + { + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true } + }, + { + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false } + }, + { + SQLSRV_ERROR_FETCH_PAST_END, + { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, " + "no more data may be retrieved.", -13, false } + }, + { + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false } + }, + { + SQLSRV_ERROR_NO_FIELDS, + { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false } + }, + + { + SQLSRV_ERROR_FETCH_NOT_CALLED, + { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch " + "functions was called.", -16, false } + }, + { + SQLSRV_ERROR_NO_DATA, + { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true } + }, + { + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true } + }, + { + SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, + { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false } + }, + { + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false } + }, + { + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" + " escaped with another right brace (}}).", -21, false } + }, + { + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces " + "must be escaped with another right brace (}}).", -22, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true } + }, + { + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " + "key/value pairs.", -25, false } + }, + { + SQLSRV_ERROR_INVALID_CONNECTION_KEY, + { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false } + }, + + { + SQLSRV_ERROR_INVALID_TYPE, + { IMSSP, (SQLCHAR*) "Invalid type.", -27, false } + }, + + { + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false } + }, + + { + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_KEY, + { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true } + }, + { + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR, + { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants " + "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false } + }, + { + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false } + }, + { + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false } + }, + { + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false } + }, + { + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or " + "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false } + }, + { + PDO_SQLSRV_ERROR_PARAM_PARSE, + { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false } + }, + { + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false } + }, + { + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only " + "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true } + }, + { + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types " + "can take an encoding option.", -49, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an " + "encoding option.", -51, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true } + }, + { + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the " + "$driver_options array of PDO::prepare.", -53, false } + }, + { + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true } + }, + { + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output " + "parameter %1!d!.", -55, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.", + -56, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an " + "explicit length.", -57, true } + }, + { + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false } + }, + { + /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related + errors so having a mapping here */ + + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " + "streams.", -59, false} + }, + { + SQLSRV_ERROR_STREAM_CREATE, + { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false } + }, + { + SQLSRV_ERROR_MARS_OFF, + { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " + "To make the connection available for other queries, either fetch all results or cancel or free the statement. " + "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false } + }, + { + SQLSRV_ERROR_FIELD_INDEX_ERROR, + { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its " + "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true } + }, + { + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false } + }, + { + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false } + }, + { + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .", + -66, true } + }, + { + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.", + -67, true } + }, + { + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of " + "PDO::prepare.", -68, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to " + "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false } + }, + { + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not " + "positive. Only positive numbers are valid for this attribute.", -70, false } + }, + { + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } + }, + { UINT_MAX, {} } +}; + +// Returns a sqlsrv_error for a given error code. +sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { + + sqlsrv_error_const *error_message = NULL; + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; + if( zr == FAILURE ) { + DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); + } + + SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); + + return error_message; +} + +// PDO error handler for the environment context. +bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); + pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); + + switch( dbh->error_mode ) { + + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + + default: + DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); + break; + } + + // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count + // return error ignored = true for warnings. + return ( warning ? true : false ); + +} + +// pdo error handler for the dbh context. +bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + SQLSRV_ASSERT(strlen(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); + strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); + + switch( dbh->error_mode ) { + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_WARNING: + if( !warning ) { + size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + MAX_DIGITS + 1; + sqlsrv_malloc_auto_ptr msg; + msg = static_cast( sqlsrv_malloc( msg_len )); + core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, + error->native_message ); + php_error( E_WARNING, msg ); + sqlsrv_free( msg ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_SILENT: + ctx.set_last_error( error ); + break; + default: + DIE( "Unknown error mode. %1!d!", dbh->error_mode ); + break; + } + + // return error ignored = true for warnings. + return ( warning ? true : false ); +} + +// PDO error handler for the statement context. +bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + SQLSRV_ASSERT( strlen( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); + strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); + + switch( pdo_stmt->dbh->error_mode ) { + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_WARNING: + if( !warning ) { + size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + MAX_DIGITS + 1; + sqlsrv_malloc_auto_ptr msg; + msg = static_cast( sqlsrv_malloc( msg_len )); + core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, + error->native_message ); + php_error( E_WARNING, msg ); + sqlsrv_free( msg ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_SILENT: + ctx.set_last_error( error ); + break; + default: + DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); + break; + } + + // return error ignored = true for warnings. + return ( warning ? true : false ); +} + + +// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: +// 0, native code +// 1, native message +// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP') + +void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ) +{ + if( last_error ) { + + // SQLSTATE is already present in the zval. + add_next_index_long( pdo_zval, last_error->native_code ); + add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); + } + else { + add_next_index_null( pdo_zval ); /* native code */ + add_next_index_null( pdo_zval ); /* native message */ + } + +} + +// Formats the error message and writes to the php error log. +void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) +{ + if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { + return; + } + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_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 ); +} + +namespace { + +void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) +{ + zval ex_obj; + ZVAL_UNDEF( &ex_obj ); + zend_class_entry* ex_class = pdo_get_exception_class(); + + int zr = object_init_ex( &ex_obj, ex_class ); + SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); + + sqlsrv_malloc_auto_ptr ex_msg; + size_t ex_msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + 12 + 1; // 12 = "SQLSTATE[]: " + ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); + snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, + ex_msg TSRMLS_CC ); + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, + reinterpret_cast( error->sqlstate ) TSRMLS_CC ); + + zval ex_error_info; + ZVAL_UNDEF( &ex_error_info ); + array_init( &ex_error_info ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); + add_next_index_long( &ex_error_info, error->native_code ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); + zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, + &ex_error_info TSRMLS_CC ); + + zend_throw_exception_object( &ex_obj TSRMLS_CC ); + ex_msg.transferred(); +} + +} diff --git a/pdo_sqlsrv/template.rc b/pdo_sqlsrv/template.rc new file mode 100644 index 00000000..fd6ea640 --- /dev/null +++ b/pdo_sqlsrv/template.rc @@ -0,0 +1,83 @@ +//---------------------------------------------------------------------------------------------------------------------------------- +// File: template.rc +// +// Contents: Version resource +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifdef APSTUDIO_INVOKED +# error dont edit with MSVC +#endif + +#include "winresrc.h" +#include "main/php_version.h" +#include "version.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifndef THANKS_GUYS +# define THANKS_GUYS "" +#endif + +#ifdef WANT_LOGO +0 ICON win32\build\php.ico +#endif + +#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build +#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel +#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) +#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) + +//Version +VS_VERSION_INFO VERSIONINFO + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" + VALUE "CompanyName", "Microsoft Corp.\0" + VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) + VALUE "InternalName", FILE_NAME "\0" + VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" + VALUE "OriginalFilename", FILE_NAME "\0" + VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD) + VALUE "URL", "http://www.microsoft.com\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#ifdef MC_INCLUDE +#include MC_INCLUDE +#endif + diff --git a/pdo_sqlsrv/version.h b/pdo_sqlsrv/version.h new file mode 100644 index 00000000..c3d06906 --- /dev/null +++ b/pdo_sqlsrv/version.h @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: version.h +// Contents: Version number constants +// +// Microsoft Drivers 4.0 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. +//--------------------------------------------------------------------------------------------------------------------------------- + +#define VER_FILEVERSION_STR "4.0.0.0" +#define _FILEVERSION 4,0,0,0 +#define SQLVERSION_MAJOR 4 +#define SQLVERSION_MINOR 0 +#define SQLVERSION_MMDD 0 +#define SQLVERSION_REVISION 0 + + + diff --git a/sqlsrv/conn.cpp b/sqlsrv/conn.cpp index 6d116b38..0c1dab6f 100644 --- a/sqlsrv/conn.cpp +++ b/sqlsrv/conn.cpp @@ -890,9 +890,9 @@ PHP_FUNCTION( sqlsrv_prepare ) core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); //mark_params_by_reference( stmt, params_z TSRMLS_CC ); - stmt->params_z = params_z; if (params_z) { - Z_TRY_ADDREF_P(params_z); + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); } stmt->prepared = true; @@ -1010,9 +1010,9 @@ PHP_FUNCTION( sqlsrv_query ) ss_stmt_options_ht, SS_STMT_OPTS, ss_error_handler, NULL TSRMLS_CC ) ); - stmt->params_z = params_z; if( params_z ) { - Z_TRY_ADDREF_P(params_z); + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); } stmt->set_func( "sqlsrv_query" ); @@ -1026,7 +1026,7 @@ PHP_FUNCTION( sqlsrv_query ) ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); // store the resource id with the connection so the connection // can release this statement when it closes. - zend_long next_index = zend_hash_next_free_element( conn->stmts ); + zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); stmt->conn_index = next_index; diff --git a/sqlsrv/core_conn.cpp b/sqlsrv/core_conn.cpp index 3bdfa868..89f213b4 100644 --- a/sqlsrv/core_conn.cpp +++ b/sqlsrv/core_conn.cpp @@ -344,7 +344,7 @@ void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSR SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - (int) sql_len, &wsql_len ); + static_cast( sql_len ), &wsql_len ); CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { throw core::CoreException(); @@ -375,7 +375,7 @@ void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_versi SQLSMALLINT buffer_len = 0; get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - ZVAL_STRINGL( server_version, buffer, buffer_len); + core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); if (NULL != buffer) { sqlsrv_free( buffer ); } @@ -739,7 +739,7 @@ void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, // Values = ("true" or "1") are treated as true values. Everything else is treated as false. // Returns 1 for true and 0 for false. -int core_str_zval_is_true( zval* value_z ) +size_t core_str_zval_is_true( zval* value_z ) { SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); @@ -755,8 +755,7 @@ int core_str_zval_is_true( zval* value_z ) } // save adjustments to the value made by stripping whitespace at the end - // TODO - review value_in - ZVAL_STRINGL( value_z, value_in, val_len); + core::sqlsrv_zval_stringl( value_z, value_in, val_len); const char VALID_TRUE_VALUE_1[] = "true"; const char VALID_TRUE_VALUE_2[] = "1"; diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp index 4cd83df9..befa124e 100644 --- a/sqlsrv/core_results.cpp +++ b/sqlsrv/core_results.cpp @@ -439,7 +439,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS // read the data into the cache // (offset from the above loop has the size of the row buffer necessary) - zend_ulong mem_used = 0; + zend_long mem_used = 0; unsigned long row_count = 0; while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { @@ -1250,8 +1250,8 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b break; } - SQLULEN already_read = 0; - SQLULEN to_read = INITIAL_FIELD_STRING_LEN; + SQLLEN already_read = 0; + SQLLEN to_read = INITIAL_FIELD_STRING_LEN; sqlsrv_malloc_auto_ptr buffer; buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); SQLRETURN r = SQL_SUCCESS; diff --git a/sqlsrv/core_sqlsrv.h b/sqlsrv/core_sqlsrv.h index 76140a8f..6858b1c9 100644 --- a/sqlsrv/core_sqlsrv.h +++ b/sqlsrv/core_sqlsrv.h @@ -1130,7 +1130,7 @@ void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval* server_info TSR void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ); void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ); bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); -int core_str_zval_is_true( zval* str_zval ); +size_t core_str_zval_is_true( zval* str_zval ); //********************************************************************************************************************************* // Statement @@ -1243,7 +1243,7 @@ struct sqlsrv_stmt : public sqlsrv_context { bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the // last results unsigned long query_timeout; // maximum allowed statement execution time - unsigned long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) + zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving @@ -1317,8 +1317,8 @@ void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_p __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ); bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC ); +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, SQLULEN cursor_type TSRMLS_DC ); void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); @@ -1394,8 +1394,8 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { // default maximum amount of memory that a buffered query can consume #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const unsigned long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const long BUFFERED_QUERY_LIMIT_INVALID = 0; + static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB + static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); virtual ~sqlsrv_buffered_result_set( void ); @@ -1505,11 +1505,12 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { // utility functions shared by multiple callers across files bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); +bool validate_string(char* string, SQLLEN& len); bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, __out unsigned int* utf16_len ); - //********************************************************************************************************************************* // Error handling routines and Predefined Errors //********************************************************************************************************************************* @@ -2059,6 +2060,22 @@ namespace core { // *** zend wrappers *** + + // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string + // so allocated memory inside of value_z should be released before assigning it to the new string + inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) + { + if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { + zend_string* temp_zstr = zend_string_init(str, str_len, 0); + zend_string_release(Z_STR_P(value_z)); + ZVAL_NEW_STR(value_z, temp_zstr); + } + else { + ZVAL_STRINGL(value_z, str, str_len); + } + } + + // exception thrown when a zend function wrapped here fails. // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw diff --git a/sqlsrv/core_stmt.cpp b/sqlsrv/core_stmt.cpp index b3e915a5..16f0a950 100644 --- a/sqlsrv/core_stmt.cpp +++ b/sqlsrv/core_stmt.cpp @@ -487,11 +487,12 @@ void core_sqlsrv_bind_param(sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLI if( direction != SQL_PARAM_INPUT ) { // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, // we reallocate the string if it's interned - if (IS_INTERNED(static_cast(buffer))) { - ZVAL_STRINGL( param_z, static_cast(buffer), buffer_len); + if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { + core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); buffer = Z_STRVAL_P( param_z ); buffer_len = Z_STRLEN_P( param_z ); } + // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, // convert it to wchar first @@ -507,7 +508,6 @@ void core_sqlsrv_bind_param(sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLI param_num + 1, get_last_error_message() ) { throw core::CoreException(); } - sqlsrv_free( buffer ); buffer = Z_STRVAL_P( param_z ); buffer_len = Z_STRLEN_P( param_z ); ind_ptr = buffer_len; @@ -564,6 +564,11 @@ void core_sqlsrv_bind_param(sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLI zval buffer_z; zval format_z; zval params[1]; + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); + bool valid_class_name_found = false; zend_class_entry *class_entry = Z_OBJCE_P(param_z TSRMLS_CC); @@ -591,17 +596,17 @@ void core_sqlsrv_bind_param(sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLI // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' // sql type, it lacks the timezone. if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - ZVAL_STRINGL( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), DateTime::DATETIMEOFFSET_FORMAT_LEN); } else if( sql_type == SQL_TYPE_DATE ) { - ZVAL_STRINGL( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); } else { - ZVAL_STRINGL( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); } // call the DateTime::format member function to convert the object to a string that SQL Server understands - ZVAL_STRINGL( &function_z, "format", sizeof( "format" ) - 1); + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); params[0] = format_z; // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the // DateTime object and $format_z is the format string. @@ -633,7 +638,6 @@ void core_sqlsrv_bind_param(sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLI core::SQLBindParameter( stmt, param_num + 1, direction, c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); - } catch( core::CoreException& e ) { stmt->free_param_data( TSRMLS_C ); @@ -945,8 +949,6 @@ void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_ // Retrieve the data core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - - // if the user wants us to cache the field, we'll do it if( cache_field ) { field_cache cache( *field_value, *field_len, sqlsrv_php_type ); @@ -1058,7 +1060,7 @@ void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* para } //Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC ) +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, SQLULEN cursor_type TSRMLS_DC ) { try { @@ -1119,7 +1121,7 @@ void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRML THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); } - stmt->buffered_query_limit = (unsigned long)limit; + stmt->buffered_query_limit = limit; } @@ -1492,7 +1494,6 @@ void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), field_len, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw core::CoreException(); } @@ -1508,7 +1509,6 @@ void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, *field_value = field_value_temp; field_value_temp.transferred(); - break; } @@ -1535,7 +1535,6 @@ void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, *field_value = field_value_temp; field_value_temp.transferred(); - break; } @@ -1555,8 +1554,11 @@ void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, zval function_z; zval_auto_ptr return_value_z; - + ZVAL_UNDEF( &field_value_temp_z ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( params ); return_value_z = (zval *)sqlsrv_malloc( sizeof(zval) ); + ZVAL_UNDEF( return_value_z ); SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); @@ -1573,8 +1575,8 @@ void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, } // Convert the string date to a DateTime object - ZVAL_STRINGL( &field_value_temp_z, field_value_temp, *field_len ); - ZVAL_STRINGL( &function_z, "date_create", sizeof("date_create") -1 ); + core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); + core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") -1 ); params[0] = field_value_temp_z; if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, @@ -1707,13 +1709,13 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z // if the string is empty, then just return that the conversion succeeded as // MultiByteToWideChar will "fail" on an empty string. if( buffer_len == 0 ) { - ZVAL_STRINGL( converted_param_z, "", 0 ); + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); return true; } // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, - reinterpret_cast( buffer ), (int) buffer_len, NULL, 0 ); + reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); // if there was a problem determining the size of the string, return false if( wchar_size == 0 ) { @@ -1723,15 +1725,15 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); // convert the utf-8 string to a wchar string in the new buffer int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - (int) buffer_len, wbuffer, wchar_size ); + static_cast( buffer_len ), wbuffer, wchar_size ); // if there was a problem converting the string, then free the memory and return false if( r == 0 ) { return false; } // null terminate the string, set the size within the zval, and return success - wbuffer[wchar_size] = L'\0'; - ZVAL_STRINGL( converted_param_z, reinterpret_cast( wbuffer.get() ), + wbuffer[ wchar_size ] = L'\0'; + core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), wchar_size * sizeof( wchar_t ) ); sqlsrv_free(wbuffer); wbuffer.transferred(); @@ -1989,8 +1991,7 @@ void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) // if it's not in the 8 bit encodings, then it's in UTF-16 if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - - bool converted = convert_string_from_utf16_inplace(output_param->encoding, &str, str_len); + bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { throw core::CoreException(); } @@ -2000,10 +2001,11 @@ void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) // so we do that here if the length of the returned data is less than the original allocation. The // original allocation null terminates the buffer already. str[ str_len ] = '\0'; + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + else { + core::sqlsrv_zval_stringl(value_z, str, str_len); } - // set the string length - ZVAL_STRINGL( value_z, str, str_len); - sqlsrv_free(str); } break; case IS_LONG: @@ -2016,7 +2018,7 @@ void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) } else { - ZVAL_LONG(value_z, static_cast(Z_LVAL_P(value_z))); + ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); } break; case IS_DOUBLE: @@ -2344,7 +2346,6 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULE { SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); buffer_len = Z_STRLEN_P( param_z ); - buffer = Z_STRVAL_P( param_z ); SQLLEN expected_len; SQLLEN buffer_null_extra; SQLLEN elem_size; @@ -2375,20 +2376,24 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULE // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about // not having a NULL terminator on a string. - buffer = static_cast( sqlsrv_realloc( buffer, expected_len )); - buffer_len = expected_len; // set the buffer_len to the new allocation size (includes the null terminator taken out below) + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. - ZVAL_STRINGL( param_z, reinterpret_cast( buffer ), without_null_len); - sqlsrv_free(buffer); + // null terminate the string to avoid a warning in debug PHP builds - (static_cast(buffer))[ without_null_len ] = '\0'; - } + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len -= buffer_null_extra; + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + } + + buffer = Z_STRVAL_P(param_z); // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which // may be less than the size of the buffer since the output may be more than the input. If it is greater, @@ -2398,7 +2403,6 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULE } } - // output parameters have their reference count incremented so that they do not disappear // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) @@ -2421,13 +2425,10 @@ void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) // called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed - void sqlsrv_output_param_dtor( zval* data ) { sqlsrv_output_param *output_param = reinterpret_cast( Z_PTR_P(data) ); zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold - efree( output_param ); - output_param = NULL; } // called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed diff --git a/sqlsrv/core_util.cpp b/sqlsrv/core_util.cpp index 588e0a6b..d533d281 100644 --- a/sqlsrv/core_util.cpp +++ b/sqlsrv/core_util.cpp @@ -72,22 +72,15 @@ void core_sqlsrv_register_logger( log_callback driver_logger ) bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) { - SQLSRV_ASSERT( string != NULL && *string != NULL, "String must be specified" ); + SQLSRV_ASSERT( string != NULL, "String must be specified" ); - // for the empty string, we simply returned we converted it - if( len == 0 && *string[0] == '\0' ) { - return true; - } + if (validate_string(*string, len)) { + return true; + } char* outString = NULL; SQLLEN outLen = 0; - if( (len/sizeof(wchar_t)) > INT_MAX ) - { - LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - bool result = convert_string_from_utf16( encoding, reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); @@ -101,6 +94,48 @@ bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, return result; } +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) +{ + char* string = Z_STRVAL_P(value_z); + + if (validate_string(string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16(encoding, + reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); + + if (result) + { + core::sqlsrv_zval_stringl(value_z, outString, outLen); + sqlsrv_free(outString); + len = outLen; + } + + return result; +} + +bool validate_string(char* string, SQLLEN& len) +{ + SQLSRV_ASSERT(string != NULL, "String must be specified"); + + //for the empty string, we simply returned we converted it + if (len == 0 && string[0] == '\0') { + return true; + } + + if ((len / sizeof(wchar_t)) > INT_MAX) + { + LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + + return false; +} + bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) { SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); @@ -147,7 +182,6 @@ bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inStrin return true; } - // thin wrapper around convert_string_from_default_encoding that handles // allocation of the destination string. An empty string passed in returns // failure since it's a failure case for convert_string_from_default_encoding. diff --git a/sqlsrv/php_sqlsrv.h b/sqlsrv/php_sqlsrv.h index cebc19f8..b69b138a 100644 --- a/sqlsrv/php_sqlsrv.h +++ b/sqlsrv/php_sqlsrv.h @@ -178,7 +178,7 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); bool prepared; // whether the statement has been prepared yet (used for error messages) - zend_long conn_index; // index into the connection hash that contains this statement structure + zend_ulong conn_index; // index into the connection hash that contains this statement structure zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys int fetch_fields_count; diff --git a/sqlsrv/stmt.cpp b/sqlsrv/stmt.cpp index b41c2c4b..1bdf5864 100644 --- a/sqlsrv/stmt.cpp +++ b/sqlsrv/stmt.cpp @@ -140,7 +140,7 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) } if( params_z ) { zval_ptr_dtor( params_z ); - params_z = NULL; + sqlsrv_free(params_z); } } @@ -294,7 +294,7 @@ PHP_FUNCTION( sqlsrv_execute ) bind_params( stmt TSRMLS_CC ); core_sqlsrv_execute( stmt TSRMLS_CC ); - + RETURN_TRUE; } catch( core::CoreException& ) { @@ -405,7 +405,7 @@ PHP_FUNCTION( sqlsrv_fetch_array ) if( !result ) { RETURN_NULL(); } - + fetch_fields_common( stmt, fetch_type, return_value, true /*allow_empty_field_names*/ TSRMLS_CC ); } @@ -759,10 +759,8 @@ PHP_FUNCTION( sqlsrv_fetch_object ) LOG_FUNCTION( "sqlsrv_fetch_object" ); ss_sqlsrv_stmt* stmt = NULL; - zval class_name_z; - ZVAL_UNDEF(&class_name_z); - zval ctor_params_z; - ZVAL_UNDEF(&ctor_params_z); + zval* class_name_z = NULL; + zval* ctor_params_z = NULL; int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied zend_long fetch_offset = 0; // default value for parameter if one isn't supplied @@ -784,17 +782,17 @@ PHP_FUNCTION( sqlsrv_fetch_object ) throw ss::SSException(); } - if( !Z_ISUNDEF(class_name_z) ) { + if( class_name_z ) { - CHECK_CUSTOM_ERROR(( Z_TYPE_P( &class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); } - class_name = Z_STRVAL( class_name_z ); - class_name_len = Z_STRLEN( class_name_z ); + class_name = Z_STRVAL( *class_name_z ); + class_name_len = Z_STRLEN( *class_name_z ); zend_str_tolower( class_name, class_name_len ); } - if( !Z_ISUNDEF(ctor_params_z) && Z_TYPE_P( &ctor_params_z ) != IS_ARRAY ) { + if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) { THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); } @@ -845,22 +843,22 @@ PHP_FUNCTION( sqlsrv_fetch_object ) if( class_entry->constructor ) { // take the parameters given as our last argument and put them into a sequential array - sqlsrv_malloc_auto_ptr params_m; + sqlsrv_malloc_auto_ptr params_m; zval ctor_retval_z; ZVAL_UNDEF( &ctor_retval_z ); int num_params = 0; - if ( !Z_ISUNDEF( ctor_params_z ) ) { - HashTable* ctor_params_ht = Z_ARRVAL( ctor_params_z ); + if ( ctor_params_z ) { + HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z ); num_params = zend_hash_num_elements( ctor_params_ht ); - params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval* ) )); + params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval ) )); int i; for( i = 0, zend_hash_internal_pointer_reset( ctor_params_ht ); - zend_hash_has_more_elements( ctor_params_ht ) == SUCCESS; - zend_hash_move_forward( ctor_params_ht ), ++i ) { - - zr = ( NULL != (params_m[i] = zend_hash_get_current_data_ex(ctor_params_ht, NULL))) ? SUCCESS : FAILURE; + zend_hash_has_more_elements( ctor_params_ht ) == SUCCESS; + zend_hash_move_forward( ctor_params_ht ), ++i ) { + ZVAL_COPY_VALUE( ¶ms_m[i], zend_hash_get_current_data_ex(ctor_params_ht, &ctor_params_ht->nInternalPointer ) ); + zr = ( NULL != ¶ms_m[i] ) ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { throw ss::SSException(); } @@ -877,7 +875,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) ZVAL_UNDEF( &(fci.function_name) ); fci.retval = &ctor_retval_z; fci.param_count = num_params; - fci.params = *params_m; // purposefully not transferred since ownership isn't actually transferred. + fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. fci.object = reinterpret_cast(return_value); @@ -1415,8 +1413,7 @@ PHP_FUNCTION( sqlsrv_free_stmt ) // delete the resource from Zend's master list, which will trigger the statement's destructor - int zr = zend_list_close(Z_RES_P(stmt_r)); - if( zr == FAILURE ) { + if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); } @@ -1443,7 +1440,7 @@ void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* } const char* scroll_type = Z_STRVAL_P( value_z ); - unsigned int cursor_type = -1; + SQLULEN cursor_type = -1; // find which cursor type they would like and set the ODBC statement attribute as such if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { @@ -1485,11 +1482,10 @@ namespace { zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len ) { zval* out_zval = NULL; - if (in_val == NULL) { - out_zval = (zval*)sqlsrv_malloc(sizeof(zval)); - ZVAL_NULL(out_zval); + out_zval = ( zval* )sqlsrv_malloc(sizeof( zval )); + ZVAL_NULL( out_zval ); return out_zval; } @@ -1498,22 +1494,24 @@ zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN fiel case SQLSRV_PHPTYPE_INT: case SQLSRV_PHPTYPE_FLOAT: { - out_zval = (zval*)sqlsrv_malloc(sizeof(zval)); - if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( out_zval, *(static_cast( in_val ))); - } - else { - ZVAL_DOUBLE( out_zval, *(static_cast( in_val ))); - } - sqlsrv_free( in_val ); + out_zval = ( zval* )sqlsrv_malloc(sizeof( zval )); + ZVAL_UNDEF( out_zval ); + if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { + ZVAL_LONG( out_zval, *( static_cast( in_val ))); + } + else { + ZVAL_DOUBLE( out_zval, *( static_cast( in_val ))); + } + sqlsrv_free( in_val ); break; } case SQLSRV_PHPTYPE_STRING: { - out_zval = (zval*) sqlsrv_malloc( sizeof(zval) ); - ZVAL_STRINGL( out_zval, reinterpret_cast( in_val ), field_len); - sqlsrv_free( in_val ); + out_zval = ( zval* )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( out_zval ); + core::sqlsrv_zval_stringl( out_zval, reinterpret_cast( in_val ), field_len ); + sqlsrv_free( in_val ); break; } @@ -1521,12 +1519,12 @@ zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN fiel case SQLSRV_PHPTYPE_DATETIME : { out_zval = (reinterpret_cast( in_val )); - in_val = NULL; + in_val = NULL; break; } case SQLSRV_PHPTYPE_NULL: - out_zval = (zval*)sqlsrv_malloc(sizeof(zval)); + out_zval = ( zval* )sqlsrv_malloc(sizeof( zval )); ZVAL_NULL( out_zval ); break; diff --git a/sqlsrv/util.cpp b/sqlsrv/util.cpp index 7abec510..fb4c4ca3 100644 --- a/sqlsrv/util.cpp +++ b/sqlsrv/util.cpp @@ -719,7 +719,7 @@ void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reporte // sqlstate zval temp; ZVAL_UNDEF(&temp); - ZVAL_STRINGL( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); + core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); //TODO: reference? Z_TRY_ADDREF_P( &temp ); if( add_next_index_zval( error_z, &temp ) == FAILURE ) {