diff --git a/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi b/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi index 7604b35d..8fdaed54 100644 Binary files a/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi and b/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi differ diff --git a/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi b/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi index c31fc209..126179ae 100644 Binary files a/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi and b/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi differ diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 9b8dbd5f..991f4635 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -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"); - } + } } diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index a9bf253a..310b66a4 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -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, {} } }; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 29a68ff9..55b4a116 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -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( 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 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( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); } #else r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast( 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; } diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 0175042b..0c909b44 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -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, diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 47e45b93..b8dd765d 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -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, {} } }; diff --git a/test/functional/pdo_sqlsrv/pdo_connect_driver.phpt b/test/functional/pdo_sqlsrv/pdo_connect_driver.phpt new file mode 100644 index 00000000..6f53e50b --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_driver.phpt @@ -0,0 +1,158 @@ +--TEST-- +Test new connection keyword Driver with valid and invalid values +--SKIPIF-- + +--FILE-- +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 \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_driver.phpt b/test/functional/sqlsrv/sqlsrv_connect_driver.phpt index 006508b2..b3c57cab 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_driver.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_driver.phpt @@ -4,42 +4,46 @@ Test new connection keyword Driver with valid and invalid values --FILE-- $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); } ?>