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:
commit
ce8d444977
Binary file not shown.
Binary file not shown.
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, {} }
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, {} }
|
||||
};
|
||||
|
|
158
test/functional/pdo_sqlsrv/pdo_connect_driver.phpt
Normal file
158
test/functional/pdo_sqlsrv/pdo_connect_driver.phpt
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
Loading…
Reference in a new issue