Merge branch 'refactorPDOTests' of https://github.com/v-kaywon/msphpsql into refactorPDOTests

This commit is contained in:
v-kaywon 2017-10-04 14:38:26 -07:00
commit e9e19fdf4b
19 changed files with 886 additions and 258 deletions

View file

@ -20,10 +20,10 @@ env:
- TEST_PHP_SQL_PWD=Password123
before_install:
- docker pull microsoft/mssql-server-linux
- docker pull microsoft/mssql-server-linux:2017-latest
install:
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d microsoft/mssql-server-linux
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d microsoft/mssql-server-linux:2017-latest
- docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql .
before_script:

View file

@ -1,3 +1,6 @@
The ODBC driver 17 preview binaries in this directory are required in order to use Always Encrypted (AE) functionality. Please note that these drivers should be considered to be preview versions -- they should not be used in production and are not supported by Microsoft. They will be replaced upon the official release of ODBC driver 17.
To install on Windows, simply run the msi. For instructions on installing the binaries on Linux platforms, please see [this page](https://github.com/Microsoft/msphpsql/wiki/Install-and-configuration#odbc-17-linux-installation).
The ODBC driver 17 preview binaries in this directory are required in order to use Always Encrypted functionality. Please note that these drivers should be considered to be preview versions -- they should not be used in production and are not supported by Microsoft. They will be replaced upon the official release of ODBC driver 17.
On Windows, the ODBC 17 preview binaries require the Visual C/C++ 2013 runtime libraries installed separately. These are installed with the [Visual Studio C++ 2013 Redistributable](https://www.microsoft.com/en-ca/download/details.aspx?id=40784) or with the [SQL Server command line utilities](https://www.microsoft.com/en-ca/download/details.aspx?id=53591). Once you have these, simply run the msi to install.
For instructions on installing the binaries on Linux platforms, please see [this page](https://github.com/Microsoft/msphpsql/wiki/Install-and-configuration#odbc-17-linux-installation).

View file

@ -26,7 +26,7 @@ if( PHP_PDO_SQLSRV != "no" ) {
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")&&
CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_PDO_SQLSRV", configure_module_dirname + "\\shared")) {
CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_PDO_SQLSRV", configure_module_dirname + "\\shared")) {
CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_PDO_SQLSRV_ODBC");
CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_PDO_SQLSRV_ODBC");
ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "pdo_sqlsrv" );
@ -40,5 +40,5 @@ if( PHP_PDO_SQLSRV != "no" ) {
EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
} else {
WARNING("pdo-sqlsrv not enabled; libraries and headers not found");
}
}
}

View file

@ -1463,7 +1463,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
quotes_needed = 3;
}
for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) {
for ( size_t index = 0; index < unquoted_len; ++index ) {
if ( unquoted[ index ] == '\'' ) {
++quotes_needed;
}
@ -1480,7 +1480,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
// insert initial quote
( *quoted )[ out_current++ ] = '\'';
for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) {
for ( size_t index = 0; index < unquoted_len; ++index ) {
if ( unquoted[ index ] == '\'' ) {
( *quoted )[ out_current++ ] = '\'';
( *quoted )[ out_current++ ] = '\'';

View file

@ -1067,6 +1067,33 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC )
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" );
// Return the correct error in case the user calls nextRowset() on a null result set.
// Null means that SQLNumResultCols() returns 0 and SQLRowCount does not return > 0. But first
// check that the statement has been executed and that we are not past the end of a non-null
// result set to make sure the user gets the correct error message. These checks are also
// done in core_sqlsrv_next_result(), but we cannot check for null results there because that
// function can be called without calling this one, and SQLSRV_ERROR_NO_FIELDS can then
// be triggered incorrectly.
CHECK_CUSTOM_ERROR( !driver_stmt->executed, driver_stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( driver_stmt->past_next_result_end, driver_stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) {
throw core::CoreException();
}
// Now make sure the result set is not null.
bool has_result = core_sqlsrv_has_any_result( driver_stmt );
// Note that if fetch_called is false but has_result is true (i.e. the user is calling
// nextRowset() on a non-null result set before calling fetch()), it is handled
// in core_sqlsrv_next_result() below.
if( !driver_stmt->fetch_called ) {
CHECK_CUSTOM_ERROR( !has_result, driver_stmt, SQLSRV_ERROR_NO_FIELDS ) {
throw core::CoreException();
}
}
core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) TSRMLS_CC );
// clear the current meta data since the new result will generate new meta data

View file

@ -398,13 +398,17 @@ pdo_error PDO_ERRORS[] = {
{ IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -77, false}
},
{
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -78, false }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -79, true }
},
{
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
{ IMSSP, (SQLCHAR*) "The specified ODBC Driver is not found.", -80, false }
},
{ UINT_MAX, {} }
};

View file

@ -110,9 +110,9 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
#endif // _WIN32
try {
// Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider
// the connection string attributes to set (enable/disable) connection pooling.
// Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section.
// Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider
// the connection string attributes to set (enable/disable) connection pooling.
// Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section.
#ifndef _WIN32
char pooling_string[ 128 ] = {0};
@ -125,9 +125,9 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
is_pooled = true;
}
#else
// 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.
// 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 ) {
@ -150,34 +150,94 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC );
bool is_missing_driver = false;
if ( conn->is_driver_set ) {
r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled );
// If column encryption is enabled, must use ODBC driver 17
if( conn->ce_option.enabled && conn->driver_version != ODBC_DRIVER_UNKNOWN) {
CHECK_CUSTOM_ERROR( conn->driver_version != ODBC_DRIVER_17, conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch() ) {
throw core::CoreException();
}
}
else if ( conn->ce_option.enabled ) {
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ];
r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled );
CHECK_CUSTOM_ERROR( is_missing_driver, conn, SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, get_processor_arch()) {
// In non-Windows environment, unixODBC 2.3.4 and unixODBC 2.3.1 return different error states when an ODBC driver exists or not
// Therefore, it is unreliable to check for a certain sql state error
#ifndef _WIN32
if( conn->driver_version != ODBC_DRIVER_UNKNOWN ) {
// check if the ODBC driver actually exists, if not, throw an exception
CHECK_CUSTOM_ERROR( ! core_search_odbc_driver_unix( conn->driver_version ), conn, SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND ) {
throw core::CoreException();
}
r = core_odbc_connect( conn, conn_str, is_pooled );
}
else {
if( conn->ce_option.enabled ) {
// driver not specified, so check if ODBC 17 exists
CHECK_CUSTOM_ERROR( ! core_search_odbc_driver_unix( ODBC_DRIVER_17 ), conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch()) {
throw core::CoreException();
}
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ODBC_DRIVER_17];
r = core_odbc_connect( conn, conn_str, is_pooled );
}
else {
// skip ODBC 11 in a non-Windows environment -- only available in Red Hat / SUSE (preview)
// https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server#microsoft-odbc-driver-11-for-sql-server-on-linux
DRIVER_VERSION odbc_version = ODBC_DRIVER_UNKNOWN;
if( core_search_odbc_driver_unix( ODBC_DRIVER_13 ) ) {
odbc_version = ODBC_DRIVER_13;
}
else if ( core_search_odbc_driver_unix( ODBC_DRIVER_17 ) ) {
odbc_version = ODBC_DRIVER_17;
}
CHECK_CUSTOM_ERROR( odbc_version == ODBC_DRIVER_UNKNOWN, conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) {
throw core::CoreException();
}
std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[odbc_version];
r = core_odbc_connect( conn, conn_str_driver, is_pooled );
} // else ce_option enabled
} // else driver_version not unknown
#else
if( conn->driver_version != ODBC_DRIVER_UNKNOWN ) {
r = core_odbc_connect( conn, conn_str, is_pooled );
// check if the specified ODBC driver is there
CHECK_CUSTOM_ERROR( core_compare_error_state( conn, r, "IM002" ), conn, SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND ) {
throw core::CoreException();
}
}
else {
if( conn->ce_option.enabled ) {
// driver not specified, so connect using ODBC 17
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ODBC_DRIVER_17];
r = core_odbc_connect( conn, conn_str, is_pooled );
for ( std::size_t i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) {
is_missing_driver = false;
std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION(i) ];
r = core_odbc_connect( conn, conn_str_driver, is_missing_driver, is_pooled );
CHECK_CUSTOM_ERROR( is_missing_driver && ( i == DRIVER_VERSION::LAST ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
// check if the specified ODBC driver is there
CHECK_CUSTOM_ERROR( core_compare_error_state( conn, r, "IM002" ) , conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch() ) {
throw core::CoreException();
}
if ( !is_missing_driver) {
break;
}
} // for
} // else ce_option enabled
}
else {
bool done = false;
for( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST && ! done; ++i ) {
std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[i];
r = core_odbc_connect( conn, conn_str_driver, is_pooled );
if( SQL_SUCCEEDED( r ) || ! core_compare_error_state( conn, r, "IM002" ) ) {
// something else went wrong, exit the loop now other than ODBC driver not found
done = true;
}
else {
// did it fail to find the last valid ODBC driver?
CHECK_CUSTOM_ERROR( ( i == DRIVER_VERSION::LAST ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
}
} // for
} // else ce_option enabled
} // else driver_version not unknown
#endif // !_WIN32
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
@ -233,17 +293,79 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
return return_conn;
}
// core_compare_error_state
// This method compares the error state to the one specified
// Parameters:
// conn - the connection structure on which we establish the connection
// rc - ODBC return code
// Return - a boolean flag that indicates if the error states are the same
bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ const char* error_state )
{
if( SQL_SUCCEEDED( rc ) )
return false;
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
return ( SQL_SUCCEEDED(sr) && ! strcmp(error_state, reinterpret_cast<char*>( state ) ) );
}
// core_search_odbc_driver_unix
// This method is meant to be used in a non-Windows environment,
// searching for a particular ODBC driver name in the odbcinst.ini file
// Parameters:
// driver_version - a valid value in enum DRIVER_VERSION
// Return - a boolean flag that indicates if the specified driver version is found or not
bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version )
{
char szBuf[DEFAULT_CONN_STR_LEN+1]; // use a large enough buffer size
WORD cbBufMax = DEFAULT_CONN_STR_LEN;
WORD cbBufOut;
char *pszBuf = szBuf;
bool found_driver = false;
#ifndef _WIN32
// get all the names of the installed drivers delimited by null characters
if(! SQLGetInstalledDrivers( szBuf, cbBufMax, &cbBufOut ) )
{
return false;
}
// extract the ODBC driver name
std::string driver = CONNECTION_STRING_DRIVER_NAME[driver_version];
std::size_t pos1 = driver.find_first_of("{");
std::size_t pos2 = driver.find_first_of("}");
std::string driver_str = driver.substr( pos1 + 1, pos2 - pos1 - 1);
// search for the ODBC driver...
const char* driver_name = driver_str.c_str();
do
{
if( strstr( pszBuf, driver_name ) != 0 )
{
return true;
}
// get the next driver
pszBuf = strchr( pszBuf, '\0' ) + 1;
}
while( pszBuf[1] != '\0' ); // end when there are two consecutive null characters
#endif // !_WIN32
return false;
}
// core_odbc_connect
// calls odbc connect API to establish the connection to server
// Parameters:
// conn - The connection structure on which we establish the connection
// conn_str - Connection string
// missing_driver_error - indicates whether odbc driver is installed on client machine
// is_pooled - indicate whether it is a pooled connection
// Return - SQLRETURN status returned by SQLDriverConnect
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled )
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled )
{
SQLRETURN r = SQL_SUCCESS;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wconn_string;
@ -262,29 +384,20 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con
#ifndef _WIN32
// unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled.
// connection handle has been allocated using henv_cp, means pooling enabled in a PHP script
if (is_pooled)
{
if (is_pooled) {
r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
else
{
else {
r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
#else
r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
#endif // !_WIN32
// clear the connection string from memory to remove sensitive data (such as a password).
// clear the connection string from memory
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if (!SQL_SUCCEEDED(r)) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// sql state IM002/IM003 in ODBC 17, means that the correct ODBC driver is not installed
is_missing_driver = ( SQL_SUCCEEDED(sr) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && (state[4] == '2' || state[4] == '3'));
}
return r;
}
@ -908,10 +1021,19 @@ void driver_set_func::func( _In_ connection_option const* option, _In_ zval* val
std::string driver_option( "" );
common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option TSRMLS_CC );
CHECK_CUSTOM_ERROR( std::find( CONNECTION_STRING_DRIVER_NAME.begin(), CONNECTION_STRING_DRIVER_NAME.end(), driver_option) == CONNECTION_STRING_DRIVER_NAME.end(), conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str){
conn->driver_version = ODBC_DRIVER_UNKNOWN;
for ( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST && conn->driver_version == ODBC_DRIVER_UNKNOWN; ++i ) {
std::string driver_name = CONNECTION_STRING_DRIVER_NAME[i];
if (! driver_name.compare( driver_option ) ) {
conn->driver_version = DRIVER_VERSION( i );
}
}
CHECK_CUSTOM_ERROR( conn->driver_version == ODBC_DRIVER_UNKNOWN, conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str) {
throw core::CoreException();
}
conn->is_driver_set = true;
conn_str += driver_option;
}

View file

@ -1039,7 +1039,8 @@ enum SERVER_VERSION {
// supported driver versions.
// the latest RTWed ODBC is the first one
enum DRIVER_VERSION : std::size_t{
enum DRIVER_VERSION {
ODBC_DRIVER_UNKNOWN = -1,
FIRST = 0,
ODBC_DRIVER_13 = FIRST,
ODBC_DRIVER_11 = 1,
@ -1073,14 +1074,14 @@ struct sqlsrv_conn : public sqlsrv_context {
SERVER_VERSION server_version; // version of the server that we're connected to
col_encryption_option ce_option; // holds the details of what are required to enable column encryption
bool is_driver_set;
DRIVER_VERSION driver_version; // version of ODBC driver
// initialize with default values
sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
server_version = SERVER_VERSION_UNKNOWN;
is_driver_set = false;
driver_version = ODBC_DRIVER_UNKNOWN;
}
// sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be
@ -1235,7 +1236,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
_Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[],
_In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC );
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled );
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled );
void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC );
void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
@ -1247,6 +1248,8 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_
bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len );
size_t core_str_zval_is_true( _Inout_ zval* str_zval );
bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t value_len );
bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version );
bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN r, _In_ const char* error_state );
//*********************************************************************************************************************************
// Statement
@ -1639,8 +1642,9 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_ODBC,
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
SQLSRV_ERROR_ZEND_HASH,
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,

View file

@ -1057,7 +1057,7 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin
CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) {
throw core::CoreException();
}
close_active_stream( stmt TSRMLS_CC );
//Clear column sql types and sql display sizes.

View file

@ -561,6 +561,32 @@ PHP_FUNCTION( sqlsrv_next_result )
try {
// Return the correct error in case the user calls sqlsrv_next_result() on a null result set.
// Null means that SQLNumResultCols() returns 0 and SQLRowCount does not return > 0. But first
// check that the statement has been executed and that we are not past the end of a non-null
// result set to make sure the user gets the correct error message. These checks are also
// done in core_sqlsrv_next_result(), but we cannot check for null results there because that
// function can be called without calling this one, and SQLSRV_ERROR_NO_FIELDS can then
// be triggered incorrectly.
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();
}
bool has_result = core_sqlsrv_has_any_result( stmt );
// Note that if fetch_called is false but has_result is true (i.e. the user is calling
// sqlsrv_next_result() on a non-null result set before calling fetch()), it is handled
// in core_sqlsrv_next_result() below.
if( !stmt->fetch_called ) {
CHECK_CUSTOM_ERROR( !has_result, stmt, SQLSRV_ERROR_NO_FIELDS ) {
throw core::CoreException();
}
}
core_sqlsrv_next_result( stmt TSRMLS_CC, true );
if( stmt->past_next_result_end ) {

View file

@ -397,13 +397,17 @@ ss_error SS_ERRORS[] = {
{ IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -104, false}
},
{
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -105, false }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -106, true }
},
{
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
{ IMSSP, (SQLCHAR*) "The specified ODBC Driver is not found.", -107, false }
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }
};

View file

@ -49,7 +49,7 @@ if [ $PLATFORM = "Ubuntu16" ]; then
yes | sudo apt-get install -qq unixodbc-dev >> env_setup.log
printf "done\n"
printf "Installing pyodbc"
printf "Installing pyodbc..."
pip3 install --upgrade pip >> env_setup.log
pip3 install pyodbc >> env_setup.log
printf "done\n"
@ -79,7 +79,7 @@ elif [ $PLATFORM = "RedHat7" ]; then
(yes | sudo yum install -y unixODBC-devel autoconf >> env_setup.log)
printf "done\n"
printf "Installing pyodbc"
printf "Installing pyodbc..."
pip3 install --upgrade pip >> env_setup.log
pip3 install pyodbc >> env_setup.log
printf "done\n"
@ -89,8 +89,8 @@ elif [ $PLATFORM = "SUSE12" ]; then
sudo zypper refresh >> env_setup.log
printf "done\n"
printf "Installing autoconf, gcc, g++, git, zip, libxml, openssl, python3, pip3..."
sudo zypper -n install autoconf gcc gcc-c++ libxml2-devel git zip libopenssl-devel python3-devel python3-pip python3-setuptools >> env_setup.log
printf "Installing autoconf, gcc, g++, git, zip, libxml, openssl, python3..."
sudo zypper -n install autoconf gcc gcc-c++ libxml2-devel git zip libopenssl-devel python3-devel python3-setuptools >> env_setup.log
printf "done\n"
printf "Installing MSODBCSQL..."
@ -101,9 +101,12 @@ elif [ $PLATFORM = "SUSE12" ]; then
zypper -n install unixODBC-devel >> env_setup.log
printf "done\n"
printf "Installing pyodbc"
pip3 install --upgrade pip >> env_setup.log
pip3 install pyodbc >> env_setup.log
printf "Installing pyodbc..."
wget https://github.com/mkleehammer/pyodbc/archive/master.zip
unzip master.zip
cd pyodbc-master/
python3 setup.py build >> env_setup.log
sudo python3 setup.py install >> env_setup.log
printf "done\n"
elif [ $PLATFORM = "Sierra" ]; then

View file

@ -0,0 +1,158 @@
--TEST--
Test new connection keyword Driver with valid and invalid values
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once('MsSetup.inc');
try {
$conn = new PDO("sqlsrv:server = $server", $uid, $pwd);
$msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer'];
$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0];
} catch(PDOException $e) {
echo "Failed to connect\n";
print_r($e->getMessage());
echo "\n";
}
$conn = null;
// start test
testValidValues();
testInvalidValues();
testEncryptedWithODBC();
testWrongODBC();
echo "Done";
// end test
///////////////////////////
function connectVerifyOutput($connectionOptions, $expected = '')
{
global $server, $uid, $pwd;
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionOptions", $uid, $pwd);
} catch(PDOException $e) {
if (strpos($e->getMessage(), $expected) === false) {
print_r($e->getMessage());
echo "\n";
}
}
}
function testValidValues()
{
global $msodbcsqlMaj;
$value = "";
// The major version number of ODBC 11 can be 11 or 12
// Test with {}
switch ($msodbcsqlMaj) {
case 17:
$value = "{ODBC Driver 17 for SQL Server}";
break;
case 13:
$value = "{ODBC Driver 13 for SQL Server}";
break;
case 12:
case 11:
$value = "{ODBC Driver 11 for SQL Server}";
break;
default:
$value = "invalid value";
}
$connectionOptions = "Driver = $value";
connectVerifyOutput($connectionOptions);
// Test without {}
switch ($msodbcsqlMaj) {
case 17:
$value = "ODBC Driver 17 for SQL Server";
break;
case 13:
$value = "ODBC Driver 13 for SQL Server";
break;
case 12:
case 11:
$value = "ODBC Driver 11 for SQL Server";
break;
default:
$value = "invalid value";
}
$connectionOptions = "Driver = $value";
connectVerifyOutput($connectionOptions);
}
function testInvalidValues()
{
$values = array("{SQL Server Native Client 11.0}",
"SQL Server Native Client 11.0",
"ODBC Driver 00 for SQL Server",
123,
false);
foreach ($values as $value) {
$connectionOptions = "Driver = $value";
$expected = "Invalid value $value was specified for Driver option.";
connectVerifyOutput($connectionOptions, $expected);
}
}
function testEncryptedWithODBC()
{
global $msodbcsqlMaj, $server, $uid, $pwd;
$value = "ODBC Driver 13 for SQL Server";
$connectionOptions = "Driver = $value; ColumnEncryption = Enabled;";
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
connectVerifyOutput($connectionOptions, $expected);
// TODO: the following block will change once ODBC 17 is officially released
$value = "ODBC Driver 17 for SQL Server";
$connectionOptions = "Driver = $value; ColumnEncryption = Enabled;";
$success = "Successfully connected with column encryption.";
$expected = "The specified ODBC Driver is not found.";
$message = $success;
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionOptions", $uid, $pwd);
} catch(PDOException $e) {
$message = $e->getMessage();
}
if ($msodbcsqlMaj == 17) {
// this indicates that OCBC 17 is the only available driver
if (strcmp($message, $success)) {
print_r($message);
}
} else {
// OCBC 17 might or might not exist
if (strcmp($message, $success)) {
if (strpos($message, $expected) === false) {
print_r($message);
}
}
}
}
function testWrongODBC()
{
global $msodbcsqlMaj;
// TODO: this will change once ODBC 17 is officially released
$value = "ODBC Driver 17 for SQL Server";
if ($msodbcsqlMaj == 17 || $msodbcsqlMaj < 13) {
$value = "ODBC Driver 13 for SQL Server";
}
$connectionOptions = "Driver = $value;";
$expected = "The specified ODBC Driver is not found.";
connectVerifyOutput($connectionOptions, $expected);
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,100 @@
--TEST--
Error messages from null result sets
--DESCRIPTION--
Test that calling nextRowset() on an empty result set produces the correct error message. Fix for Github 507.
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon.inc");
$conn = new PDO( "sqlsrv:Server = $server; Database = $databaseName; ", $uid, $pwd );
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
DropTable($conn, 'TestEmptySetTable');
$stmt = $conn->query("CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))");
$stmt = $conn->query("INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')");
// Create a procedure that can return a result set or can return nothing
DropProc($conn, 'TestEmptySetProc');
$stmt = $conn->query("CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10)
AS SET NOCOUNT ON
BEGIN
IF @b='b'
BEGIN
SELECT 'a' as testValue
END
ELSE
BEGIN
UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a
END
END");
// errors out when reaching the second nextRowset() call
// returned error indicates there are no more results
echo "Return a nonempty result set:\n";
try
{
$stmt = $conn->query("TestEmptySetProc @a='a', @b='b'");
$result = $stmt->fetchAll();
print_r($result);
$stmt->nextRowset();
$result = $stmt->fetchAll();
print_r($result);
$stmt->nextRowset();
}
catch(Exception $e)
{
echo $e->getMessage()."\n";
}
// errors out indicating the result set contains no fields
echo "Return an empty result set, call nextRowset on it before fetching anything:\n";
try
{
$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'");
$stmt->nextRowset();
}
catch(Exception $e)
{
echo $e->getMessage()."\n";
}
// errors out indicating the result set contains no fields
echo "Return an empty result set, call fetch on it:\n";
try
{
$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'");
$result = $stmt->fetchAll();
print_r($result);
}
catch(Exception $e)
{
echo $e->getMessage()."\n";
}
$stmt = $conn->query("DROP TABLE TestEmptySetTable");
$stmt = $conn->query("DROP PROCEDURE TestEmptySetProc");
$conn = null;
?>
--EXPECT--
Return a nonempty result set:
Array
(
[0] => Array
(
[testValue] => a
[0] => a
)
)
Array
(
)
SQLSTATE[IMSSP]: There are no more results returned by the query.
Return an empty result set, call nextRowset on it before fetching anything:
SQLSTATE[IMSSP]: The active result for the query contains no fields.
Return an empty result set, call fetch on it:
SQLSTATE[IMSSP]: The active result for the query contains no fields.

View file

@ -4,42 +4,46 @@ Test new connection keyword Driver with valid and invalid values
<?php require('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure( 'WarningsReturnAsErrors', 0 );
require( 'MsSetup.inc' );
sqlsrv_configure('WarningsReturnAsErrors', 0);
require('MsSetup.inc');
$connectionOptions = array("Database"=>$database,"UID"=>$userName, "PWD"=>$userPassword);
$connectionOptions = array("Database"=>$database, "UID"=>$userName, "PWD"=>$userPassword);
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
{
print_r(sqlsrv_errors());
}
$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer'];
$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0];
sqlsrv_close($conn);
// start test
test_valid_values($msodbcsql_maj,$server ,$connectionOptions);
test_invalid_values($msodbcsql_maj,$server ,$connectionOptions);
testValidValues($msodbcsqlMaj, $server, $connectionOptions);
testInvalidValues($msodbcsqlMaj, $server, $connectionOptions);
testEncryptedWithODBC($msodbcsqlMaj, $server, $connectionOptions);
testWrongODBC($msodbcsqlMaj, $server, $connectionOptions);
echo "Done";
// end test
///////////////////////////
function connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected = '' )
function connectVerifyOutput($server, $connectionOptions, $expected = '')
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
{
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false )
if(strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false)
{
print_r(sqlsrv_errors());
}
}
}
function test_valid_values( $msodbcsql_maj ,$server ,$connectionOptions){
function testValidValues($msodbcsqlMaj, $server, $connectionOptions)
{
$value = "";
// The major version number of ODBC 11 can be 11 or 12
// Test with {}
switch ( $msodbcsql_maj )
switch ($msodbcsqlMaj)
{
case 17:
$value = "{ODBC Driver 17 for SQL Server}";
@ -55,10 +59,10 @@ function test_valid_values( $msodbcsql_maj ,$server ,$connectionOptions){
$value = "invalid value";
}
$connectionOptions['Driver']=$value;
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions );
connectVerifyOutput($server, $connectionOptions);
// Test without {}
switch ( $msodbcsql_maj )
switch ($msodbcsqlMaj)
{
case 17:
$value = "ODBC Driver 17 for SQL Server";
@ -75,35 +79,84 @@ function test_valid_values( $msodbcsql_maj ,$server ,$connectionOptions){
}
$connectionOptions['Driver']=$value;
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions );
connectVerifyOutput($server, $connectionOptions);
}
function test_invalid_values($msodbcsql_maj ,$server ,$connectionOptions){
// test invalid value
$value = "{SQL Server Native Client 11.0}";
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
function testInvalidValues($msodbcsqlMaj, $server, $connectionOptions)
{
$values = array("{SQL Server Native Client 11.0}",
"SQL Server Native Client 11.0",
"ODBC Driver 00 for SQL Server");
$value = "SQL Server Native Client 11.0";
foreach ($values as $value) {
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connectVerifyOutput($server, $connectionOptions, $expected);
}
$values = array(123, false);
foreach ($values as $value) {
$connectionOptions['Driver']=$value;
$expected = "Invalid value type for option Driver was specified. String type was expected.";
connectVerifyOutput($server, $connectionOptions, $expected);
}
}
function testEncryptedWithODBC($msodbcsqlMaj, $server, $connectionOptions)
{
$value = "ODBC Driver 13 for SQL Server";
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$connectionOptions['ColumnEncryption']='Enabled';
$value = "ODBC Driver 00 for SQL Server";
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
$value = 123;
$connectionOptions['Driver']=$value;
$expected = "Invalid value type for option Driver was specified. String type was expected.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
connectVerifyOutput($server, $connectionOptions, $expected);
$value = false;
// TODO: the following block will change once ODBC 17 is officially released
$value = "ODBC Driver 17 for SQL Server";
$connectionOptions['Driver']=$value;
$expected = "Invalid value type for option Driver was specified. String type was expected.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$connectionOptions['ColumnEncryption']='Enabled';
$success = "Successfully connected with column encryption.";
$expected = "The specified ODBC Driver is not found.";
$message = $success;
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
$message = sqlsrv_errors($conn)[0]['message'];
if ($msodbcsqlMaj == 17)
{
// this indicates that OCBC 17 is the only available driver
if (strcmp($message, $success))
print_r($message);
}
else
{
// OCBC 17 might or might not exist
if (strcmp($message, $success))
{
if (strpos($message, $expected) === false)
print_r($message);
}
}
}
function testWrongODBC($msodbcsqlMaj, $server, $connectionOptions)
{
// TODO: this will change once ODBC 17 is officially released
$value = "ODBC Driver 17 for SQL Server";
if ($msodbcsqlMaj == 17 || $msodbcsqlMaj < 13)
{
$value = "ODBC Driver 13 for SQL Server";
}
$connectionOptions['Driver']=$value;
$expected = "The specified ODBC Driver is not found.";
connectVerifyOutput($server, $connectionOptions, $expected);
}
?>

View file

@ -0,0 +1,124 @@
--TEST--
Error messages from null result sets
--DESCRIPTION--
Test that calling sqlsrv_next_result() on a null result set produces the correct error message. Fix for Github 507.
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon.inc");
$conn = sqlsrv_connect($server, array("Database"=>$databaseName, "uid"=>$uid, "pwd"=>$pwd));
DropTable($conn, 'TestEmptySetTable');
$stmt = sqlsrv_query($conn, "CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))");
$stmt = sqlsrv_query($conn, "INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')");
// Create a procedure that can return a result set or can return nothing
DropProc($conn, 'TestEmptySetProc');
$stmt = sqlsrv_query($conn, "CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10)
AS SET NOCOUNT ON
BEGIN
IF @b='b'
BEGIN
SELECT 'a' as testValue
END
ELSE
BEGIN
UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a
END
END");
// errors out when reaching the second nextRowset() call
// returned error indicates there are no more results
echo "Return a nonempty result set:\n";
$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'");
$result = sqlsrv_fetch_array($stmt);
print_r($result);
sqlsrv_next_result($stmt);
$result = sqlsrv_fetch_array($stmt);
print_r($result);
sqlsrv_next_result($stmt);
print_r(sqlsrv_errors());
// errors out indicating the result set contains no fields
echo "Return an empty result set, call nextRowset on it before fetching anything:\n";
$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'");
sqlsrv_next_result($stmt);
print_r(sqlsrv_errors());
// errors out indicating the result set contains no fields
echo "Return an empty result set, call fetch on it:\n";
$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'");
$result = sqlsrv_fetch_array($stmt);
print_r($result);
print_r(sqlsrv_errors());
$stmt = sqlsrv_query($conn, "DROP TABLE TestEmptySetTable");
$stmt = sqlsrv_query($conn, "DROP PROCEDURE TestEmptySetProc");
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECTF--
Return a nonempty result set:
Array
(
[0] => a
[testValue] => a
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -26
[code] => -26
[2] => There are no more results returned by the query.
[message] => There are no more results returned by the query.
)
[1] => Array
(
[0] => HY010
[SQLSTATE] => HY010
[1] => 0
[code] => 0
[2] => [%rMicrosoft|unixODBC%r][%rODBC D|D%rriver Manager]%r[ ]{0,1}%rFunction sequence error
[message] => [%rMicrosoft|unixODBC%r][%rODBC D|D%rriver Manager]%r[ ]{0,1}%rFunction sequence error
)
)
Return an empty result set, call nextRowset on it before fetching anything:
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -28
[code] => -28
[2] => The active result for the query contains no fields.
[message] => The active result for the query contains no fields.
)
)
Return an empty result set, call fetch on it:
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -28
[code] => -28
[2] => The active result for the query contains no fields.
[message] => The active result for the query contains no fields.
)
)

View file

@ -1,159 +1,159 @@
--TEST--
warnings as errors
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure( 'WarningsReturnAsErrors', 0 );
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
require( 'MsCommon.inc' );
$conn = Connect();
if( $conn === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$stmt = sqlsrv_prepare( $conn, "SELECT * FROM [cd_info]");
$result = sqlsrv_field_metadata( $stmt );
if( $result === false ) {
die( "sqlsrv_field_metadata should have succeeded." );
}
$result = sqlsrv_fetch( $stmt );
if( $result !== false ) {
die( "sqlsrv_fetch should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_fetch_array( $stmt );
if( $result !== false ) {
die( "sqlsrv_fetch_array should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_get_field( $stmt, 0 );
if( $result !== false ) {
die( "sqlsrv_get_field should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_next_result( $stmt );
if( $result !== false ) {
die( "sqlsrv_next_result should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_rows_affected( $stmt );
if( $result !== false ) {
die( "sqlsrv_rows_affected should have failed because it wasn't yet executed." );
}
// Outputting the zero element of the error array works around a bug in the
// ODBC driver for Linux that produces an error message saying 'Cancel treated
// as FreeStmt/Close' on a statement that has not been executed.
print_r( sqlsrv_errors()[0] );
sqlsrv_execute( $stmt );
$result = sqlsrv_field_metadata( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
print_r( sqlsrv_errors() );
$result = sqlsrv_rows_affected( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_fetch_array( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_fetch( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_get_field( $stmt, 0 );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_next_result( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
sqlsrv_free_stmt( $stmt );
sqlsrv_close( $conn );
print "Test successful";
?>
--EXPECT--
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
Test successful
--TEST--
warnings as errors
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure( 'WarningsReturnAsErrors', 0 );
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
require( 'MsCommon.inc' );
$conn = Connect();
if( $conn === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$stmt = sqlsrv_prepare( $conn, "SELECT * FROM [cd_info]");
$result = sqlsrv_field_metadata( $stmt );
if( $result === false ) {
die( "sqlsrv_field_metadata should have succeeded." );
}
$result = sqlsrv_fetch( $stmt );
if( $result !== false ) {
die( "sqlsrv_fetch should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_fetch_array( $stmt );
if( $result !== false ) {
die( "sqlsrv_fetch_array should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_get_field( $stmt, 0 );
if( $result !== false ) {
die( "sqlsrv_get_field should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_next_result( $stmt );
if( $result !== false ) {
die( "sqlsrv_next_result should have failed because it wasn't yet executed." );
}
print_r( sqlsrv_errors() );
$result = sqlsrv_rows_affected( $stmt );
if( $result !== false ) {
die( "sqlsrv_rows_affected should have failed because it wasn't yet executed." );
}
// Outputting the zero element of the error array works around a bug in the
// ODBC driver for Linux that produces an error message saying 'Cancel treated
// as FreeStmt/Close' on a statement that has not been executed.
print_r( sqlsrv_errors()[0] );
sqlsrv_execute( $stmt );
$result = sqlsrv_field_metadata( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
print_r( sqlsrv_errors() );
$result = sqlsrv_rows_affected( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_fetch_array( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_fetch( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_get_field( $stmt, 0 );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$result = sqlsrv_next_result( $stmt );
if( $result === false ) {
die( print_r( sqlsrv_errors(), true ));
}
sqlsrv_free_stmt( $stmt );
sqlsrv_close( $conn );
print "Test successful";
?>
--EXPECT--
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
)
Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -11
[code] => -11
[2] => The statement must be executed before results can be retrieved.
[message] => The statement must be executed before results can be retrieved.
)
Test successful