Merge pull request #552 from yitam/missingDriver

Search for installed ODBC driver(s) in non-Windows env and more error handling when driver option is set
This commit is contained in:
Jenny Tam 2017-10-03 15:05:49 -07:00 committed by GitHub
commit ce8d444977
9 changed files with 429 additions and 84 deletions

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

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

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

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

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