Merge pull request #1338 from microsoft/dev

5.10.0-beta2
This commit is contained in:
Jenny Tam 2021-12-02 09:54:56 -08:00 committed by GitHub
commit 36250ea551
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1359 additions and 392 deletions

View file

@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## 5.10.0-beta2 - 2021-12-02
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.1
- Support for Ubuntu 21.10
- Feature Request [#1320](https://github.com/microsoft/msphpsql/issues/1320) - allow PDO::ATTR_EMULATE_PREPARES to be set at the connection level
### Fixed
- Issue [#1307](https://github.com/microsoft/msphpsql/issues/1307) - added TVP support to non-procedure statements
- Issue [#1310](https://github.com/microsoft/msphpsql/issues/1310) - adjusted sql_data_type and column size for NULL parameters - pull request [#1311](https://github.com/microsoft/msphpsql/pull/1311) by gjcarrette
- Pull request [#1326](https://github.com/microsoft/msphpsql/pull/1326) - php drivers simply pass Azure AD Authentication to ODBC driver, which will verify the settings
- Issue [#1329](https://github.com/microsoft/msphpsql/issues/1329) - reset sql type and column size for input params
- Issue [#1331](https://github.com/microsoft/msphpsql/issues/1331) - restore PDO::ATTR_ERRMODE if calling PDO::lastInsertId() call fails - pull request [#1330](https://github.com/microsoft/msphpsql/pull/1330) by mpyw and pull request [#1332](https://github.com/microsoft/msphpsql/pull/1332)
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.10.0-beta1 - 2021-09-08
Updated PECL release packages. Here is the list of updates:

View file

@ -206,10 +206,12 @@ jobs:
coveralls -i ./source/ -e ./source/shared/ -e ./test/ -e ./source/pdo_sqlsrv/shared/core_stream.cpp \
-E r'.*localization*' -E r'.*globalization*' --gcov-options '\-lp'
displayName: 'Invoke coveralls using repo token'
condition: false
env:
COVERALLS_REPO_TOKEN: $(repo_token)
TRAVIS_JOB_ID: $(Build.BuildId)
TRAVIS_BRANCH: $(Build.SourceBranchName)
PYTHONWARNINGS: ignore::yaml.YAMLLoadWarning
- script: |
cd $(Build.SourcesDirectory)/test/functional/

View file

@ -25,7 +25,7 @@ def write_index(index_filename, tag_version):
PATH=%var2%
SRCSRVTRG=%TARG%\%PDBVERSION%\%fnbksl%(%var2%)
SRCURL=https://raw.githubusercontent.com/Microsoft/msphpsql/%SRCVERSION%/source/%PATH%
SRCSRVCMD=powershell -Command "$r=New-Object -ComObject Msxml2.XMLHTTP; $r.open('GET', '%SRCURL%', $false); $r.send(); [io.file]::WriteAllBytes('%SRCSRVTRG%', $r.responseBody)"
SRCSRVCMD=powershell -Command "Invoke-WebRequest -Uri '%SRCURL%' -OutFile '%SRCSRVTRG%'"
SRCVERSION=v5.6.0
PDBVERSION=v5.6.0
For example
@ -38,9 +38,7 @@ def write_index(index_filename, tag_version):
f.write('SRCSRVTRG=%TARG%\%PDBVERSION%\%fnbksl%(%var2%)' + os.linesep)
f.write('SRCURL=https://raw.githubusercontent.com/Microsoft/msphpsql/%SRCVERSION%/source/%PATH%' + os.linesep)
f.write('SRCSRVCMD=powershell -Command ')
f.write('\"$r=New-Object -ComObject Msxml2.XMLHTTP; ')
f.write('$r.open(\'GET\', \'%SRCURL%\', $false); ')
f.write('$r.send(); [io.file]::WriteAllBytes(\'%SRCSRVTRG%\', $r.responseBody)\"' + os.linesep)
f.write('\"Invoke-WebRequest -Uri \'%SRCURL%\' -OutFile \'%SRCSRVTRG%\'\"' + os.linesep)
f.write('SRCVERSION=' + tag_version + os.linesep)
f.write('PDBVERSION=' + tag_version + os.linesep)

View file

@ -542,7 +542,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
fetch_datetime( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED)
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED),
emulate_prepare(false)
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
@ -734,7 +735,8 @@ bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr,
// assign the methods for the statement object. This is necessary even if the
// statement fails so the user can retrieve the error information.
stmt->methods = &pdo_sqlsrv_stmt_methods;
stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names
// if not emulate_prepare, we support parameterized queries with ?, not names
stmt->supports_placeholders = (driver_dbh->emulate_prepare) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; // the statement options may override this later
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_stmt_options_ht );
@ -1288,8 +1290,15 @@ bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR );
}
// Statement level only
case PDO_ATTR_EMULATE_PREPARES:
{
driver_dbh->emulate_prepare = zend_is_true(val);
if (driver_dbh->emulate_prepare && driver_dbh->ce_option.enabled) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
}
}
break;
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
@ -1362,8 +1371,13 @@ int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_
#endif
}
// Statement level only
case PDO_ATTR_EMULATE_PREPARES:
{
ZVAL_BOOL(return_value, driver_dbh->emulate_prepare);
break;
}
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
@ -1584,6 +1598,9 @@ zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_str
driver_stmt->~sqlsrv_stmt();
} catch( core::CoreException& ) {
// restore error handling to its previous mode
dbh->error_mode = prev_err_mode;
// copy any errors on the statement to the connection so that the user sees them, since the statement is released
// before this method returns
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
@ -1599,7 +1616,7 @@ zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_str
str[0] = '\0';
return str;
#else
return NULL;
return ZSTR_EMPTY_ALLOC();
#endif
}

View file

@ -173,31 +173,6 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast<char*>( key_name ) );
}
void conn_string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len )
{
// if the keyword is 'Authentication', check whether the user specified option is supported
bool valid = true;
if ( stricmp( this->current_key_name, ODBCConnOptions::Authentication ) == 0 ) {
if (len <= 0)
valid = false;
else {
// extract option from the value by len
sqlsrv_malloc_auto_ptr<char> option;
option = static_cast<char*>( sqlsrv_malloc( len + 1 ) );
memcpy_s( option, len + 1, value, len );
option[len] = '\0';
valid = AzureADOptions::isAuthValid(option, len);
}
}
if( !valid ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, this->current_key_name );
}
string_parser::add_key_value_pair( value, len );
}
inline bool sql_string_parser::is_placeholder_char( char c )
{
// placeholder only accepts numbers, upper and lower case alphabets and underscore

View file

@ -377,10 +377,6 @@ pdo_error PDO_ERRORS[] = {
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Statement with emulate prepare on does not support output or input_output parameters.", -72, false }
},
{
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.", -73, false }
},
{
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server (or above) for %1!s!.", -78, true }
@ -441,10 +437,6 @@ pdo_error PDO_ERRORS[] = {
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
},
{
SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL,
{ IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -93, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
{ IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -94, false}

View file

@ -149,9 +149,6 @@ class conn_string_parser : private string_parser
int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len );
void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len);
protected:
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len);
public:
conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht );
void parse_conn_string( void );
@ -194,6 +191,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn {
bool format_decimals;
short decimal_places;
short use_national_characters;
bool emulate_prepare;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver );
};
@ -390,7 +388,6 @@ enum PDO_ERROR_CODES {
PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE,
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID

View file

@ -704,41 +704,6 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v
return true;
}
namespace AzureADOptions {
enum AAD_AUTH_TYPE {
MIN_AAD_AUTH_TYPE = 0,
SQL_PASSWORD = 0,
AAD_PASSWORD,
AAD_MSI,
AAD_SPA,
MAX_AAD_AUTH_TYPE
};
const char *AADAuths[] = { "SqlPassword", "ActiveDirectoryPassword", "ActiveDirectoryMsi", "ActiveDirectoryServicePrincipal" };
bool isAuthValid(_In_z_ const char* value, _In_ size_t value_len)
{
if (value_len <= 0)
return false;
bool isValid = false;
for (short i = MIN_AAD_AUTH_TYPE; i < MAX_AAD_AUTH_TYPE && !isValid; i++)
{
if (!stricmp(value, AADAuths[i])) {
isValid = true;
}
}
return isValid;
}
bool isAADMsi(_In_z_ const char* value)
{
return (value != NULL && !stricmp(value, AADAuths[AAD_MSI]));
}
}
// *** internal connection functions and classes ***
namespace {
@ -792,10 +757,11 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
access_token_used = true;
}
// Check if Authentication is ActiveDirectoryMSI
// Check if Authentication is ActiveDirectoryMSI because we have to handle this case differently
// https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
bool activeDirectoryMSI = false;
if (authentication_option_used) {
const char aadMSIoption[] = "ActiveDirectoryMSI";
zval* auth_option = NULL;
auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION);
@ -804,34 +770,16 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
option = Z_STRVAL_P(auth_option);
}
//if (option != NULL && !stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) {
activeDirectoryMSI = AzureADOptions::isAADMsi(option);
if (activeDirectoryMSI) {
// There are two types of managed identities:
// (1) A system-assigned managed identity: UID must be NULL
// (2) A user-assigned managed identity: UID defined but must not be an empty string
// In both cases, PWD must be NULL
bool invalid = false;
if (pwd != NULL) {
invalid = true;
} else {
if (uid != NULL && strnlen_s(uid) == 0) {
invalid = true;
}
}
CHECK_CUSTOM_ERROR(invalid, conn, SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL ) {
throw core::CoreException();
}
if (option != NULL && !stricmp(option, aadMSIoption)) {
activeDirectoryMSI = true;
}
}
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string );
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// because they are incompatible
// If uid is not present then we use trusted connection -- but not when connecting
// using the access token or Authentication is ActiveDirectoryMSI
if (!access_token_used && !activeDirectoryMSI) {
if (uid == NULL || strnlen_s(uid) == 0) {
connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};"

View file

@ -190,11 +190,6 @@ const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3;
const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34;
const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7;
namespace AzureADOptions {
bool isAuthValid(_In_z_ const char* value, _In_ size_t value_len);
bool isAADMsi(_In_z_ const char* value);
}
// the message returned by ODBC Driver for SQL Server
const char ODBC_CONNECTION_BUSY_ERROR[] = "Connection is busy with results for another command";
@ -2011,7 +2006,6 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL,
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE,
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,

View file

@ -395,6 +395,13 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
stmt->params_container.insert_param(param_num, new_param);
param_ptr = new_param;
new_param.transferred();
} else if (direction == SQL_PARAM_INPUT
&& param_ptr->sql_data_type != SQL_SS_TABLE
&& param_ptr->strlen_or_indptr == SQL_NULL_DATA) {
// reset the followings for regular input parameters if it was bound as a null param before
param_ptr->sql_data_type = sql_type;
param_ptr->column_size = column_size;
param_ptr->strlen_or_indptr = 0;
}
SQLSRV_ASSERT(param_ptr != NULL, "core_sqlsrv_bind_param: param_ptr is null. Something went wrong.");
@ -2148,16 +2155,17 @@ void sqlsrv_param::process_null_param(_Inout_ zval* param_z)
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
// Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases,
// if the server type is a binary type, than the server expects the sql_type to be binary type
// if the server type is a binary type, then the server expects the sql_type to be binary type
// as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the
// server. For all other server types, setting the sql_type to sql_char works fine.
sql_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_BINARY : SQL_CHAR;
// server. For all other server types, setting the sql_type to sql_varchar works fine.
// It must be varchar with column size 0 for ISNULL to work properly.
sql_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_BINARY : SQL_VARCHAR;
}
c_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_C_BINARY : SQL_C_CHAR;
if (column_size == SQLSRV_UNKNOWN_SIZE) {
column_size = 1;
column_size = (encoding == SQLSRV_ENCODING_BINARY) ? 1 : 0;
decimal_digits = 0;
}
buffer = NULL;
@ -3107,8 +3115,6 @@ void sqlsrv_param_tvp::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* pa
int num_columns = parse_tv_param_arrays(stmt, param_z);
column_size = num_rows;
buffer = NULL;
buffer_length = 0;
strlen_or_indptr = (num_columns == 0)? SQL_DEFAULT_PARAM : SQL_DATA_AT_EXEC;
} else {
// This is one of the constituent columns of the table-valued parameter
@ -3154,11 +3160,16 @@ int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ z
throw core::CoreException();
}
// Save the TVP type name for SQLSetDescField later
buffer = ZSTR_VAL(tvp_name);
buffer_length = SQL_NTS;
// Check if schema is provided by the user
if (zend_hash_move_forward_ex(inputs_ht, &pos) == SUCCESS) {
zval *schema_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
if (schema_z != NULL && Z_TYPE_P(schema_z) == IS_STRING) {
schema_name = Z_STR_P(schema_z);
ZVAL_NEW_STR(&placeholder_z, schema_name);
}
}
@ -3308,6 +3319,34 @@ void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
return;
}
// Set Table-Valued parameter type name (and the schema where it is defined)
SQLHDESC hIpd = NULL;
core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0);
if (buffer != NULL) {
// SQL_CA_SS_TYPE_NAME is optional for stored procedure calls, but it must be
// specified for SQL statements that are not procedure calls to enable the
// server to determine the type of the table-valued parameter.
char *tvp_name = reinterpret_cast<char *>(buffer);
SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_TYPE_NAME, reinterpret_cast<SQLCHAR*>(tvp_name), SQL_NTS);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
}
if (Z_TYPE(placeholder_z) == IS_STRING) {
// If the table type for the table-valued parameter is defined in a different
// schema than the default, SQL_CA_SS_SCHEMA_NAME must be specified. If not,
// the server will not be able to determine the type of the table-valued parameter.
char * schema_name = Z_STRVAL(placeholder_z);
SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_SCHEMA_NAME, reinterpret_cast<SQLCHAR*>(schema_name), SQL_NTS);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
// Free and reset the placeholder_z
zend_string_release(Z_STR(placeholder_z));
ZVAL_UNDEF(&placeholder_z);
}
// Bind the TVP columns one by one
// Register this object first using SQLSetDescField() for sending TVP data post execution
SQLHDESC desc;
@ -3606,12 +3645,17 @@ bool sqlsrv_params_container::get_next_parameter(_Inout_ sqlsrv_stmt* stmt)
// Done now, reset current_param
current_param = NULL;
return false;
} else if (r == SQL_NEED_DATA) {
if (param != NULL) {
current_param = reinterpret_cast<sqlsrv_param*>(param);
SQLSRV_ASSERT(current_param != NULL, "sqlsrv_params_container::get_next_parameter - The parameter requested is missing!");
current_param->init_data_from_zval(stmt);
} else {
// Do not reset current_param when param is NULL, because
// it means that data is expected from the existing current_param
}
}
current_param = reinterpret_cast<sqlsrv_param*>(param);
SQLSRV_ASSERT(current_param != NULL, "sqlsrv_params_container::get_next_parameter - The parameter requested is missing!");
current_param->init_data_from_zval(stmt);
return true;
}

View file

@ -31,7 +31,7 @@
#define SQLVERSION_BUILD 0
// For previews, set this constant to 1, 2 and so on. Otherwise, set it to 0
#define PREVIEW 1
#define PREVIEW 2
#define SEMVER_PRERELEASE
// Semantic versioning build metadata, build meta data is not counted in precedence order.
@ -59,7 +59,7 @@
#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD
// PECL package version ('-' or '+' is not allowed) - to support Pickle do not use macros below
#define PHP_SQLSRV_VERSION "5.10.0beta1"
#define PHP_PDO_SQLSRV_VERSION "5.10.0beta1"
#define PHP_SQLSRV_VERSION "5.10.0beta2"
#define PHP_PDO_SQLSRV_VERSION "5.10.0beta2"
#endif // VERSION_H

View file

@ -1362,16 +1362,6 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In
throw ss::SSException();
}
bool valid = true;
if( stricmp( SS_CONN_OPTS[i].sqlsrv_name, SSConnOptionNames::Authentication ) == 0 ) {
valid = AzureADOptions::isAuthValid(value, value_len);
}
CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[i].sqlsrv_name ) {
throw ss::SSException();
}
break;
}
case CONN_ATTR_INVALID:

View file

@ -206,7 +206,6 @@ enum SS_ERROR_CODES {
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF,
SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED
};

View file

@ -1187,8 +1187,6 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
try {
stmt->free_param_data();
stmt->executed = false;
zval* params_z = stmt->params_z;
@ -1265,6 +1263,8 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
} ZEND_HASH_FOREACH_END();
}
catch( core::CoreException& ) {
stmt->free_param_data();
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
zval_ptr_dtor( stmt->params_z );
sqlsrv_free( stmt->params_z );

View file

@ -363,10 +363,6 @@ ss_error SS_ERRORS[] = {
"Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value."
, -61, true }
},
{
SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.", -62, false }
},
{
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED,
{ IMSSP, (SQLCHAR*)"Must specify the SQL type for each parameter in a parameterized query when using sqlsrv_query in a column encryption enabled connection.", -63, false }
@ -429,10 +425,6 @@ ss_error SS_ERRORS[] = {
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -117, false}
},
{
SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL,
{ IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -118, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
{ IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -119, false}

View file

@ -636,6 +636,25 @@ function IsDaasMode()
return ($daasMode ? true : false);
}
function isServerHGSEnabled()
{
$enabled = false;
try {
$conn = connect();
$tsql = "SELECT @@SERVERNAME";
$stmt = $conn->query($tsql);
$result = $stmt->fetch(PDO::FETCH_NUM);
$name = $result[0];
$enabled = (strpos($result[0], 'HGS') != false);
} catch (Exception $e) {
echo $e->getMessage();
die("Could not fetch server property.");
}
return $enabled;
}
function isSQLAzure()
{
// 'SQL Azure' indicates SQL Database or SQL Data Warehouse

View file

@ -1,49 +1,44 @@
--TEST--
Tests error returned when binding output parameter with emulate prepare
--DESCRIPTION--
The test shows that the option sets in prepared statements overrides the
connection setting of PDO::ATTR_EMULATE_PREPARES
--SKIPIF--
<?php require_once('skipif_mid-refactor.inc'); ?>
<?php require('skipif_azure_dw.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
require_once("MsSetup.inc");
try {
$conn = connect();
// Do not connect with AE enabled because otherwise this would have thrown a different exception
$conn = new PDO("sqlsrv:server=$server; Database = $databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$count = 0;
$query = "select ? = count(* ) from cd_info";
$stmt = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true));
$stmt->bindParam(1, $count, PDO::PARAM_STR, 10);
$stmt->execute();
echo "Result: ".$count."\n";
$query = "select bigint_type, int_type, money_type from [test_types] where int_type < 0";
$stmt1 = $conn->prepare($query);
$stmt1->execute();
$row = $stmt1->fetch(PDO::FETCH_ASSOC);
print_r($row);
$int = 0;
$bigint = 100;
$query = "select ? = bigint_type, ? = int_type, ? = money_type from [test_types] where int_type < 0";
$stmt2 = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true));
$stmt2->bindparam(1, $bigint, PDO::PARAM_STR, 256);
$stmt2->bindParam(2, $int, PDO::PARAM_INT, 4);
$stmt2->bindParam(3, $money, PDO::PARAM_STR, 1024);
$stmt2->execute();
echo "Big integer: ".$bigint."\n";
echo "Integer: ".$int."\n";
echo "Money: ".$money."\n";
//free the statement and connection
unset($stmt);
unset($stmt1);
unset($stmt2);
unset($conn);
$stmt = $conn->prepare($query);
} catch (PDOException $e) {
print("Error: " . $e->getMessage() . "\n");
}
try {
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$int = 0;
$bigint = 100;
$query = "select ? = bigint_type, ? = int_type, ? = money_type from [test_types] where int_type < 0";
$stmt = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true));
} catch (PDOException $e) {
print("Error: " . $e->getMessage() . "\n");
}
// free the statement and connection
unset($stmt);
unset($conn);
?>
--EXPECT--
Error: SQLSTATE[IMSSP]: Statement with emulate prepare on does not support output or input_output parameters.
Error: SQLSTATE[IMSSP]: Statement with emulate prepare on does not support output or input_output parameters.

View file

@ -0,0 +1,97 @@
--TEST--
Verify Github Issue 1307 is fixed.
--DESCRIPTION--
To show that table-valued parameters work with non-procedure statements
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function cleanup($conn, $tvpname, $testTable)
{
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$dropTableType = dropTableTypeSQL($conn, $tvpname);
$conn->exec($dropTableType);
$conn->exec("DROP TABLE IF EXISTS [$testTable]");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
function readData($conn, $testTable)
{
$tsql = "SELECT id FROM $testTable ORDER BY id";
$stmt = $conn->query($tsql);
$stmt->bindColumn('id', $ID);
while ($row = $stmt->fetch( PDO::FETCH_BOUND ) ){
echo $ID . PHP_EOL;
}
}
try {
$conn = new PDO("sqlsrv:Server=$server;Database=$databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tvpname = 'pdo_id_table';
$testTable = 'pdo_test_table';
cleanup($conn, $tvpname, $testTable);
// Create the table type and test table
$tsql = "CREATE TYPE $tvpname AS TABLE(id INT PRIMARY KEY)";
$conn->exec($tsql);
$tsql = "CREATE TABLE $testTable (id INT PRIMARY KEY)";
$conn->exec($tsql);
// Populate the table using the table type
$tsql = "INSERT INTO $testTable SELECT * FROM ?";
$tvpinput = array($tvpname => [[1], [2], [3]]);
$stmt = $conn->prepare($tsql);
$stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB);
$result = $stmt->execute();
// Verify the results
readData($conn, $testTable);
// Use Merge statement next
$tsql = <<<QRY
MERGE INTO $testTable t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
unset($tvpinput);
$tvpinput = array($tvpname => [[5], [4], [3], [2]]);
$stmt = $conn->prepare($tsql);
$stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB);
$result = $stmt->execute();
// Verify the results
readData($conn, $testTable);
cleanup($conn, $tvpname, $testTable);
echo "Done\n";
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e);
}
?>
--EXPECT--
1
2
3
1
2
3
4
5
Done

View file

@ -0,0 +1,105 @@
--TEST--
Verify Github Issue 1307 is fixed but TVP and table are defined in a different schema
--DESCRIPTION--
To show that table-valued parameters work with non-procedure statements
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function cleanup($conn, $tvpname, $testTable, $schema)
{
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$dropTableType = dropTableTypeSQL($conn, $tvpname, $schema);
$conn->exec($dropTableType);
$conn->exec("DROP TABLE IF EXISTS [$schema].[$testTable]");
$conn->exec("DROP SCHEMA IF EXISTS [$schema]");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
function readData($conn, $schema, $testTable)
{
$tsql = "SELECT id FROM [$schema].[$testTable] ORDER BY id";
$stmt = $conn->query($tsql);
$stmt->bindColumn('id', $ID);
while ($row = $stmt->fetch( PDO::FETCH_BOUND ) ){
echo $ID . PHP_EOL;
}
}
try {
$conn = new PDO("sqlsrv:Server=$server;Database=$databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tvpname = 'pdo_id_table2';
$testTable = 'pdo_test_table2';
$schema = 'pdo schema';
cleanup($conn, $tvpname, $testTable, $schema);
// Create the schema
$tsql = "CREATE SCHEMA [$schema]";
$conn->exec($tsql);
// Create the table type and test table
$tsql = "CREATE TYPE [$schema].[$tvpname] AS TABLE(id INT PRIMARY KEY)";
$conn->exec($tsql);
$tsql = "CREATE TABLE [$schema].[$testTable] (id INT PRIMARY KEY)";
$conn->exec($tsql);
// Populate the table using the table type
$tsql = "INSERT INTO [$schema].[$testTable] SELECT * FROM ?";
$tvpinput = array($tvpname => [[5], [3], [1]], $schema);
$stmt = $conn->prepare($tsql);
$stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB);
$result = $stmt->execute();
// Verify the results
readData($conn, $schema, $testTable);
// Use Merge statement next
$tsql = <<<QRY
MERGE INTO [$schema].[$testTable] t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
unset($tvpinput);
$tvpinput = array($tvpname => [[2], [4], [6], [7]], $schema);
$stmt = $conn->prepare($tsql);
$stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB);
$result = $stmt->execute();
// Verify the results
readData($conn, $schema, $testTable);
cleanup($conn, $tvpname, $testTable, $schema);
echo "Done\n";
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e);
}
?>
--EXPECT--
1
3
5
1
2
3
4
5
6
7
Done

View file

@ -0,0 +1,88 @@
--TEST--
GitHub issue 1310 - bind null field as varchar(max) if not binary
--DESCRIPTION--
The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102.
Note that this test does not connect with AE enabled because SQLDescribeParam() does not work with these queries.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
try {
$conn = new PDO("sqlsrv:server=$server; Database = $databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Issue 1310
$query = "SELECT CAST(ISNULL(:K, -1) AS INT) AS K";
$k = null;
$stmt = $conn->prepare($query);
$stmt->bindParam(':K', $k, PDO::PARAM_NULL);
$stmt->execute();
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($row);
$stmt->bindParam(':K', $k, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetchAll(PDO::FETCH_NUM);
var_dump($row);
// Issue 1102
$query = "DECLARE @d DATETIME = ISNULL(:K, GETDATE()); SELECT @d AS D;";
$k = null;
$stmt = $conn->prepare($query);
$stmt->bindParam(':K', $k, PDO::PARAM_NULL);
$stmt->execute();
$row = $stmt->fetchAll(PDO::FETCH_NUM);
var_dump($row);
$stmt->bindParam(':K', $k, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($row);
echo "Done\n";
} catch (PdoException $e) {
echo $e->getMessage();
}
?>
--EXPECTREGEX--
array\(1\) {
\[0\]=>
array\(1\) {
\["K"\]=>
string\(2\) "-1"
}
}
array\(1\) {
\[0\]=>
array\(1\) {
\[0\]=>
string\(2\) "-1"
}
}
array\(1\) {
\[0\]=>
array\(1\) {
\[0\]=>
string\(23\) "20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:00.000"
}
}
array\(1\) {
\[0\]=>
array\(1\) {
\["D"\]=>
string\(23\) "20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:00.000"
}
}
Done

View file

@ -0,0 +1,62 @@
--TEST--
GitHub issue 1320 - support PDO::ATTR_EMULATE_PREPARES at the connection level
--DESCRIPTION--
Supports PDO::ATTR_EMULATE_PREPARES at the connection level but setting it to true with column
encryption enabled will fail with an exception. Also, the options in the prepared statement will
override the connection setting.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
try {
// Connection with column encryption enabled
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
echo "setAttribute should have failed because column encryption is enabled.\n\n";
} catch (PDOException $e) {
echo $e->getMessage() . "\n";
}
unset($conn);
try {
// Connection with column encryption enabled
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => true);
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd, $options);
} catch (PDOException $e) {
echo $e->getMessage() . "\n";
}
unset($conn);
try {
// Connection with column encryption enabled - PDO::ATTR_EMULATE_PREPARES is false by default
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully with column encryption enabled.\n";
$enabled = $conn->getAttribute(PDO::ATTR_EMULATE_PREPARES);
echo "By default, the emulation of prepared statements is:\n";
var_dump($enabled);
} catch (PDOException $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
SQLSTATE[IMSSP]: Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.
SQLSTATE[IMSSP]: Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.
Connected successfully with column encryption enabled.
By default, the emulation of prepared statements is:
bool(false)

View file

@ -0,0 +1,118 @@
--TEST--
GitHub issue 1329 - string truncation error when binding some parameters as non-nulls the second time
--DESCRIPTION--
The test shows the same parameters, though bound as nulls in the first insertion, can be bound as non-nulls in the subsequent insertions.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
function dropTable($conn, $tableName)
{
$drop = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE $tableName";
$conn->exec($drop);
}
try {
$conn = new PDO("sqlsrv:server=$server; Database = $databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
dropTable($conn, 'domains');
$tsql = <<<CREATESQL
CREATE TABLE domains (
id bigint IDENTITY(1,1) NOT NULL,
authority nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
base_url_redirect nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
regular_not_found_redirect nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
invalid_short_url_redirect nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT PK__domains__3213E83F512B36BA PRIMARY KEY (id))
CREATESQL;
$conn->exec($tsql);
$tsql = <<<INSERTSQL
INSERT INTO domains (authority, base_url_redirect, regular_not_found_redirect, invalid_short_url_redirect) VALUES (?, ?, ?, ?)
INSERTSQL;
$stmt = $conn->prepare($tsql);
$authority = 'foo.com';
$base = null;
$notFound = null;
$invalid = null;
$stmt->bindParam(1, $authority);
$stmt->bindParam(2, $base);
$stmt->bindParam(3, $notFound);
$stmt->bindParam(4, $invalid);
$stmt->execute();
$authority = 'detached-with-ředirects.com';
$base = 'fŏő.com';
$notFound = 'baŗ.com';
$invalid = null;
$stmt->bindParam(1, $authority);
$stmt->bindParam(2, $base);
$stmt->bindParam(3, $notFound);
$stmt->bindParam(4, $invalid);
$stmt->execute();
$authority = 'Őther-redirects.com';
$base = 'fooš.com';
$notFound = null;
$invalid = 'ŷëå';
$stmt->bindParam(1, $authority);
$stmt->bindParam(2, $base);
$stmt->bindParam(3, $notFound);
$stmt->bindParam(4, $invalid);
$stmt->execute();
// fetch the data
$stmt = $conn->prepare("SELECT * FROM domains");
$stmt->execute();
$row = $stmt->fetchAll(PDO::FETCH_NUM);
print_r($row);
dropTable($conn, 'domains');
echo "Done\n";
} catch (PdoException $e) {
echo $e->getMessage();
}
?>
--EXPECT--
Array
(
[0] => Array
(
[0] => 1
[1] => foo.com
[2] =>
[3] =>
[4] =>
)
[1] => Array
(
[0] => 2
[1] => detached-with-ředirects.com
[2] => fŏő.com
[3] => baŗ.com
[4] =>
)
[2] => Array
(
[0] => 3
[1] => Őther-redirects.com
[2] => fooš.com
[3] =>
[4] => ŷëå
)
)
Done

View file

@ -41,8 +41,36 @@ EOF;
$st->execute();
$data = selectAll($cnn, $tbname);
$system_param2 = 'another string';
$utf8_param = 'Привет';
$binary_param = fopen('php://memory', 'a');
fwrite($binary_param, hex2bin('80838790a9')); // testing some extended characters
rewind($binary_param);
$st->bindParam(1, $system_param2, PDO::PARAM_STR);
$st->bindParam(2, $utf8_param, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8);
$st->bindParam(3, $binary_param, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
$st->execute();
$select = "SELECT * FROM $tbname WHERE system_encoding = ?";
$st = $cnn->prepare($select);
$st->bindParam(1, $system_param);
$st->execute();
$data = $st->fetchAll(PDO::FETCH_BOTH);
var_dump($data);
$st->bindParam(1, $system_param2);
$st->execute();
$st->bindColumn('utf8_encoding', $param2);
$st->bindColumn('binary_encoding', $param3);
$row = $st->fetch(PDO::FETCH_BOUND);
if ($param2 != $utf8_param)
echo "$param2\n";
if (bin2hex($param3) != '80838790a9')
echo "$param3\n";
dropTable($cnn, $tbname);
unset($st);

View file

@ -3,7 +3,7 @@ Test some basics of Azure AD Access Token support
--DESCRIPTION--
This test also expects certain exceptions to be thrown under some conditions.
--SKIPIF--
<?php require('skipif.inc');
<?php require('skipif_azure.inc');
require('skipif_azure_ad_acess_token.inc'); ?>
--FILE--
<?php

View file

@ -34,22 +34,6 @@ if ($stmt === false) {
unset($conn);
///////////////////////////////////////////////////////////////////////////////////////////
// Test Azure AD with integrated authentication. This should fail because
// we don't support it.
//
$connectionInfo = "Authentication = ActiveDirectoryIntegrated; TrustServerCertificate = true;";
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionInfo");
echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n";
unset($conn);
} catch (PDOException $e) {
echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n";
print_r($e->getMessage());
echo "\n";
}
///////////////////////////////////////////////////////////////////////////////////////////
// Test Azure AD on an Azure database instance. Replace $azureServer, etc with
// your credentials to test, or this part is skipped.
@ -95,6 +79,4 @@ if ($azureServer != 'TARGET_AD_SERVER') {
--EXPECTF--
Connected successfully with Authentication=SqlPassword.
string(1) "%d"
Could not connect with Authentication=ActiveDirectoryIntegrated.
SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.
%s with Authentication=ActiveDirectoryPassword.

View file

@ -18,58 +18,6 @@ function verifyErrorMessage($exception, $expectedError, $msg)
}
}
function connectWithInvalidOptions()
{
global $server;
$message = 'AzureAD Managed Identity test: expected to fail with ';
$expectedError = 'When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted';
$uid = '';
$connectionInfo = "Authentication = ActiveDirectoryMsi;";
$testCase = 'empty UID provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$pwd = '';
$connectionInfo = "Authentication = ActiveDirectoryMsi;";
$testCase = 'empty PWD provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$pwd = 'dummy';
$connectionInfo = "Authentication = ActiveDirectoryMsi;";
$testCase = 'PWD provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.';
$connectionInfo = "Authentication = ActiveDirectoryMsi; AccessToken = '123';";
$testCase = 'AccessToken option';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo");
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
}
function connectInvalidServer()
{
global $server, $driver, $uid, $pwd;
@ -102,9 +50,6 @@ function connectInvalidServer()
require_once('MsSetup.inc');
// Test some error conditions
connectWithInvalidOptions();
// Make a connection to an invalid server
connectInvalidServer();

View file

@ -8,21 +8,17 @@ the error message of one test case is not the same.
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once('MsSetup.inc');
require_once('MsCommon_mid-refactor.inc');
$msodbcsqlMaj = "";
$hgsEnabled = true;
$hgsEnabled = isServerHGSEnabled();
try {
$conn = new PDO("sqlsrv:server = $server", $uid, $pwd);
$msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer'];
$version = explode(".", $msodbcsqlVer);
$msodbcsqlMaj = $version[0];
// Next, check if the server is HGS enabled
$serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO);
if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) {
$hgsEnabled = false;
}
} catch (PDOException $e) {
echo "Failed to connect\n";
print_r($e->getMessage());
@ -32,7 +28,6 @@ try {
testColumnEncryption($server, $uid, $pwd, $msodbcsqlMaj);
echo "Done";
function verifyOutput($PDOerror, $expected, $caseNum)
{
if (strpos($PDOerror->getMessage(), $expected) === false) {

View file

@ -1,7 +1,8 @@
--TEST--
Test PDO::__Construct by passing connection options and attributes.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
<?php require('skipif_azure.inc');
require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");

View file

@ -1,7 +1,8 @@
--TEST--
Test PDO::__Construct by passing connection options
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
<?php require('skipif_azure.inc');
require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");

View file

@ -40,7 +40,7 @@ try {
?>
--EXPECTREGEX--
string\(3\) "200"
string\(3\) "102"
(string\(0\) ""|bool\(false\))
--EXPECT--
string(3) "200"
string(3) "102"
string(0) ""

View file

@ -0,0 +1,52 @@
--TEST--
Confirm that PDO::ATTR_ERRMODE value should be restored whether PDO::lastInsertId() call succeeded or not.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
// create temporary tables
createTable($conn, "table1", array(new columnMeta("int", "id", "IDENTITY(100,2)"), "val" => "int"));
createTable($conn, "table2", array(new columnMeta("int", "id", "IDENTITY(200,2)"), "val" => "int"));
createTable($conn, "table3", array("id" => "int", "val" => "int"));
insertRow($conn, "table1", array("val" => 1), "exec");
insertRow($conn, "table2", array("val" => 2), "exec");
$conn->lastInsertId();
var_dump($conn->getAttribute(PDO::ATTR_ERRMODE));
insertRow($conn, "table2", array("val" => 3), "exec");
insertRow($conn, "table1", array("val" => 4), "exec");
$conn->lastInsertId();
var_dump($conn->getAttribute(PDO::ATTR_ERRMODE));
// Should restore original value even if PDO::lastInsertId() failed.
insertRow($conn, "table3", array("id" => 1, "val" => 1), "exec");
$conn->lastInsertId();
var_dump($conn->getAttribute(PDO::ATTR_ERRMODE));
dropTable($conn, "table1");
dropTable($conn, "table2");
dropTable($conn, "table3");
// Should trigger exception
$tsql = "SELECT * FROM dummy";
$conn->exec($tsql);
unset($conn);
} catch (PDOException $e) {
print_r($e->getMessage());
exit;
}
?>
--EXPECTREGEX--
int\(2\)
int\(2\)
int\(2\)
.*Invalid object name \'dummy\'\.

View file

@ -47,17 +47,21 @@ try {
print_r($row);
//with emulate prepare and no bind param options
print_r("Prepare with emulate prepare and no bindParam options:\n");
if (!isAEConnected()) {
$options = array(PDO::ATTR_EMULATE_PREPARES => true);
} else {
$options = array(PDO::ATTR_EMULATE_PREPARES => false);
}
$stmt = prepareStmt($conn, $query, $options);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
if ($stmt->rowCount() == 0) {
print_r("No results for this query\n");
print_r("Prepare with emulate prepare and no bindParam options:\n");
// This test only makes sense without AE because the default encoding is PDO::SQLSRV_ENCODING_UTF8
if (!isAEConnected()) {
$stmt = prepareStmt($conn, $query, $options);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($stmt->rowCount() != 0) {
print_r("Do not expect results for this query!\n");
print_r($row);
}
}
//with emulate prepare and SQLSRV_ENCODING_UTF8
@ -117,7 +121,6 @@ Array
[age] => 30
)
Prepare with emulate prepare and no bindParam options:
No results for this query
Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:
Array
(

View file

@ -0,0 +1,137 @@
--TEST--
Prepare with emulate prepare and binding uft8 characters
--DESCRIPTION--
This is the same as pdo_prepare_emulatePrepare_unicode.phpt except that
PDO::ATTR_EMULATE_PREPARES at the connection level
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once('MsCommon_mid-refactor.inc');
function prepareStmt($conn, $query, $prepareOptions = array(), $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = null)
{
$name = "가각";
if (!isColEncrypted()) {
$stmt = $conn->prepare($query, $prepareOptions);
$stmt->bindParam(':name', $name, $dataType, $length, $driverOptions);
} else {
$status = 1;
$stmt = $conn->prepare($query, $prepareOptions);
$stmt->bindParam(':name', $name, $dataType, $length, $driverOptions);
$stmt->bindParam(':status', $status);
}
$stmt->execute();
return $stmt;
}
try {
$conn = connect("", array(), PDO::ERRMODE_SILENT);
$tableName = "users";
createTable($conn, $tableName, array("name" => "nvarchar(max)", "status" => "int", "age" => "int"));
if (!isColEncrypted()) {
$conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'Belle', 1, 34)");
$conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'Абрам', 1, 40)");
$conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'가각', 1, 30)");
$query = "SELECT * FROM [$tableName] WHERE name = :name AND status = 1";
} else {
insertRow($conn, $tableName, array("name" => "Belle", "status" => 1, "age" => 34));
insertRow($conn, $tableName, array("name" => "Абрам", "status" => 1, "age" => 40));
insertRow($conn, $tableName, array("name" => "가각", "status" => 1, "age" => 30));
$query = "SELECT * FROM [$tableName] WHERE name = :name AND status = :status";
}
//without emulate prepare
print_r("Prepare without emulate prepare:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
if (!isAEConnected()) {
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
} else {
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
//with emulate prepare and no bind param options
print_r("Prepare with emulate prepare and no bindParam options:\n");
// This test only makes sense without AE because the default encoding is PDO::SQLSRV_ENCODING_UTF8
if (!isAEConnected()) {
$stmt = prepareStmt($conn, $query, array());
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($stmt->rowCount() != 0) {
print_r("Do not expect results for this query!\n");
print_r($row);
}
}
//with emulate prepare and SQLSRV_ENCODING_UTF8
print_r("Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
//with emulate prepare and SQLSRV_ENCODING_SYSTEM
print_r("Prepare with emulate prepare and and SQLSRV_ENCODING_SYSTEM:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_SYSTEM);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// The combination of Column Encryption and Unix platforms support SQLSRV_ENCODING_SYSTEM because:
// With Column Encryption enabled, binding parameters uses exact datatypes as the column definition
// the default encoding in Linux and Mac is UTF8
$success = true;
if (!(strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN') && isAEConnected()) {
if ($row['name'] != "가각" || $row['status'] != 1 || $row['age'] != 30) {
print_r("Incorrect results retrieved.\n");
$success = false;
}
} else {
// the default encoding in Windows is non-UTF8, thus binding UTF8 parameters does not work
if ($stmt->rowCount() != 0) {
print_r("Binding UTF8 data when encoding is SQLSRV_ENCODING_SYSTEM should not work.\n");
$success = false;
}
}
if ($success) {
print_r("Binding UTF8 data with SQLSRV_ENCODING_SYSTEM is tested successfully.\n");
}
//with emulate prepare and encoding SQLSRV_ENCODING_BINARY
print_r("Prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
if ($stmt->rowCount() == 0) {
print_r("No results for this query\n");
}
dropTable($conn, $tableName);
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Prepare without emulate prepare:
Array
(
[name] => 가각
[status] => 1
[age] => 30
)
Prepare with emulate prepare and no bindParam options:
Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:
Array
(
[name] => 가각
[status] => 1
[age] => 30
)
Prepare with emulate prepare and and SQLSRV_ENCODING_SYSTEM:
Binding UTF8 data with SQLSRV_ENCODING_SYSTEM is tested successfully.
Prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY:
No results for this query

View file

@ -17,15 +17,6 @@ try {
$tableName = "pdo_test_table";
createTable($conn1, $tableName, array(new ColumnMeta("int", "ID", "NOT NULL PRIMARY KEY"), "Policy" => "varchar(2)", "Label" => "varchar(10)", "Budget" => "money"));
try {
$res = $conn1->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
if ($res) {
echo "setAttribute should have failed.\n\n";
}
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
try {
$query = "SELECT * FROM [$tableName]";
$stmt = $conn1->query($query);
@ -186,7 +177,6 @@ try {
?>
--EXPECTF--
SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object.
SQLSTATE[IMSSP]: An invalid attribute was designated on the PDOStatement object.
Start inserting data...
....Done....

View file

@ -6,3 +6,9 @@ require 'MsSetup.inc';
if ($daasMode) {
die("skip test not applicable in Azure\n");
}
require_once('MsCommon_mid-refactor.inc');
if (isServerHGSEnabled()) {
die("skip test not applicable in Azure VM\n");
}
?>

View file

@ -34,7 +34,7 @@ if ($msodbcsqlMin < 4 and $msodbcsqlMaj == 17) {
// Get SQL Server
$serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO);
if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) {
if (strpos($serverInfo['SQLServerName'], 'HGS') === false) {
die("skip Server is not HGS enabled");
}
?>

View file

@ -3,31 +3,31 @@ if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
require_once( "MsSetup.inc" );
require_once("MsSetup.inc");
$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd );
if( $conn === false )
{
die( "skip Could not connect during SKIPIF." );
try {
$conn = new PDO("sqlsrv:server = $server", $uid, $pwd);
} catch (PDOException $e) {
die("skip Could not connect during SKIPIF.");
}
// Get process ID. Not the same as the one during the actual test, but
// we only need to know the protocol for a particular connection.
$stmt = $conn->query( "SELECT @@SPID" );
if ( $stmt )
{
$spid = $stmt->fetch(PDO::FETCH_NUM)[0];
}
else
{
die( "skip Could not fetch SPID during SKIPIF.");
}
$tsql = <<<SQL
SELECT c.session_id, c.net_transport
FROM sys.dm_exec_connections AS c
JOIN sys.dm_exec_sessions AS s
ON c.session_id = s.session_id
WHERE c.session_id = @@SPID;
SQL;
$stmt = $conn->query( "SELECT * FROM sys.dm_exec_connections WHERE session_id = ".$spid);
$prot = $stmt->fetchColumn(3);
try {
// Check the transport protocol for the current connection
$stmt = $conn->query($tsql);
$prot = $stmt->fetchColumn(1);
if ($prot != 'TCP')
{
die("skip Not using a TCP protocol." );
if ($prot != 'TCP') {
die("skip Not using a TCP protocol.");
}
} catch (PDOException $e) {
die("skip Failed to fetch SPID and transport protocol.");
}
?>

View file

@ -94,6 +94,21 @@ function isLocaleDisabled()
return ($daasMode || $localeDisabled);
}
function isServerHGSEnabled()
{
$conn = connect();
$tsql = "SELECT @@SERVERNAME";
$stmt = sqlsrv_query($conn, $tsql);
if (sqlsrv_fetch($stmt)) {
$name = sqlsrv_get_field($stmt, 0);
if (strpos($name, 'HGS') != false) {
return true;
}
}
return false;
}
function isSQLAzure()
{
// 'SQL Azure' indicates SQL Database or SQL Data Warehouse

View file

@ -7,4 +7,8 @@ if (!extension_loaded("sqlsrv")) {
require 'MsSetup.inc';
if ($daasMode) die("skip test not applicable in Azure\n");
require 'MsCommon.inc';
if (isServerHGSEnabled()) {
die("skip test not applicable in Azure VM\n");
}
?>

View file

@ -34,7 +34,7 @@ if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) {
// Get SQL Server
$server_info = sqlsrv_server_info($conn);
if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) {
if (strpos($server_info['SQLServerName'], 'HGS') === false) {
die("skip Server is not HGS enabled");
}
?>

View file

@ -3,35 +3,32 @@ if (!extension_loaded("sqlsrv")) {
die("skip Extension not loaded");
}
require_once( "MsSetup.inc" );
require_once("MsSetup.inc");
$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword );
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword);
$conn = sqlsrv_connect( $server, $connectionInfo );
if( $conn === false )
{
die( "skip Could not connect during SKIPIF." );
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
die("skip Could not connect during SKIPIF.");
}
// Get process ID. Not the same as the one during the actual test, but
// we only need to know the protocol for a particular connection.
$stmt = sqlsrv_query( $conn, "SELECT @@SPID" );
if ( sqlsrv_fetch( $stmt ) )
{
$spid = sqlsrv_get_field( $stmt, 0 );
}
else
{
die("skip Could not fetch SPID.");
}
$tsql = <<<SQL
SELECT c.session_id, c.net_transport
FROM sys.dm_exec_connections AS c
JOIN sys.dm_exec_sessions AS s
ON c.session_id = s.session_id
WHERE c.session_id = @@SPID;
SQL;
$stmt = sqlsrv_query( $conn, "SELECT * FROM sys.dm_exec_connections WHERE session_id = $spid");
if ( sqlsrv_fetch( $stmt ) )
{
$prot = sqlsrv_get_field( $stmt, 3 );
if ($prot != 'TCP')
{
die( "skip Not using a TCP protocol." );
// Check the transport protocol for the current connection
$stmt = sqlsrv_query($conn, $tsql);
if (sqlsrv_fetch($stmt)) {
$prot = sqlsrv_get_field($stmt, 1);
if ($prot != 'TCP'){
die("skip Not using a TCP protocol.");
}
} else {
die("skip Failed to fetch SPID and transport protocol.");
}
?>

View file

@ -3,7 +3,7 @@ Test some basics of Azure AD Access Token support
--DESCRIPTION--
This test also expects certain exceptions to be thrown under some conditions.
--SKIPIF--
<?php require('skipif.inc');
<?php require('skipif_azure.inc');
require('skipif_azure_ad_acess_token.inc'); ?>
--FILE--
<?php

View file

@ -34,22 +34,6 @@ if (sqlsrv_fetch($stmt)) {
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
///////////////////////////////////////////////////////////////////////////////////////////
// Test Azure AD with integrated authentication. This should fail because
// we don't support it.
//
$connectionInfo = array( "Authentication"=>"ActiveDirectoryIntegrated", "TrustServerCertificate"=>true );
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n";
$errors = sqlsrv_errors();
print_r($errors[0]);
} else {
echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n";
sqlsrv_close($conn);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Test Azure AD on an Azure database instance. Replace $azureServer, etc with
// your credentials to test, or this part is skipped.
@ -99,14 +83,4 @@ if ($azureServer != 'TARGET_AD_SERVER') {
--EXPECTF--
Connected successfully with Authentication=SqlPassword.
string(1) "%d"
Could not connect with Authentication=ActiveDirectoryIntegrated.
Array
(
[0] => IMSSP
[SQLSTATE] => IMSSP
[1] => -62
[code] => -62
[2] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.
[message] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.
)
%s with Authentication=ActiveDirectoryPassword.

View file

@ -19,34 +19,6 @@ function verifyErrorMessage($conn, $expectedError, $msg)
}
}
function connectWithInvalidOptions()
{
global $server;
$expectedError = 'When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted';
$connectionInfo = array("UID"=>"", "Authentication" => "ActiveDirectoryMsi");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'empty UID provided');
unset($connectionInfo);
$connectionInfo = array("PWD"=>"", "Authentication" => "ActiveDirectoryMsi");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'empty PWD provided');
unset($connectionInfo);
$connectionInfo = array("PWD"=>"pwd", "Authentication" => "ActiveDirectoryMsi");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'PWD provided');
unset($connectionInfo);
$expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.';
$connectionInfo = array("Authentication"=>"ActiveDirectoryMsi", "AccessToken" => "123");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'AccessToken option');
unset($connectionInfo);
}
function connectInvalidServer()
{
global $server, $driver, $userName, $userPassword;
@ -76,9 +48,6 @@ function connectInvalidServer()
}
}
// Test some error conditions
connectWithInvalidOptions($server);
// Make a connection to an invalid server
connectInvalidServer();

View file

@ -9,7 +9,7 @@ the error message of one test case is not the same.
--FILE--
<?php
sqlsrv_configure('WarningsReturnAsErrors', 0);
require('MsSetup.inc');
require('MsCommon.inc');
$connectionOptions = array("Database"=>$database,"UID"=>$userName, "PWD"=>$userPassword);
testColumnEncryption($server, $connectionOptions);
@ -25,11 +25,7 @@ function testColumnEncryption($server, $connectionOptions)
$msodbcsqlMaj = explode(".", $msodbcsql_ver)[0];
// Next, check if the server is HGS enabled
$hgsEnabled = true;
$serverInfo = sqlsrv_server_info($conn);
if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) {
$hgsEnabled = false;
}
$hgsEnabled = isServerHGSEnabled();
// Only works for ODBC 17
$connectionOptions['ColumnEncryption'] = 'Enabled';

View file

@ -0,0 +1,111 @@
--TEST--
Verify Github Issue 1307 is fixed.
--DESCRIPTION--
To show that table-valued parameters work with non-procedure statements
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsCommon.inc");
function cleanup($conn, $tvpname, $testTable)
{
$dropTableType = dropTableTypeSQL($conn, $tvpname);
sqlsrv_query($conn, $dropTableType);
sqlsrv_query($conn, "DROP TABLE IF EXISTS [$testTable]");
}
function readData($conn, $testTable)
{
$tsql = "SELECT id FROM $testTable ORDER BY id";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
while ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
$ID = sqlsrv_get_field($stmt, 0);
echo $ID . PHP_EOL;
}
sqlsrv_free_stmt($stmt);
}
$conn = connect();
$tvpname = 'srv_id_table';
$testTable = 'srv_test_table';
cleanup($conn, $tvpname, $testTable);
// Create the table type and test table
$tsql = "CREATE TYPE $tvpname AS TABLE(id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
$tsql = "CREATE TABLE $testTable (id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
// Populate the table using the table type
$tsql = "INSERT INTO $testTable SELECT * FROM ?";
$params = [
[[$tvpname => [[5], [7], [9]]]],
];
$stmt = sqlsrv_query($conn, $tsql, $params);
if (!$stmt) {
print_r(sqlsrv_errors());
}
sqlsrv_free_stmt($stmt);
// Verify the results
readData($conn, $testTable);
// Use Merge statement next
$tsql = <<<QRY
MERGE INTO $testTable t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
$params = [
[[$tvpname => [[2], [6], [4], [8], [3]]]],
];
$stmt = sqlsrv_prepare($conn, $tsql, $params);
if (!$stmt) {
print_r(sqlsrv_errors());
}
$result = sqlsrv_execute($stmt);
if (!$result) {
print_r(sqlsrv_errors());
}
// Verify the results
readData($conn, $testTable);
cleanup($conn, $tvpname, $testTable);
echo "Done\n";
sqlsrv_close($conn);
?>
--EXPECT--
5
7
9
2
3
4
5
6
7
8
9
Done

View file

@ -0,0 +1,119 @@
--TEST--
Verify Github Issue 1307 is fixed but TVP and table are defined in a different schema
--DESCRIPTION--
To show that table-valued parameters work with non-procedure statements
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsCommon.inc");
function cleanup($conn, $tvpname, $testTable, $schema)
{
$dropTableType = dropTableTypeSQL($conn, $tvpname, $schema);
sqlsrv_query($conn, $dropTableType);
sqlsrv_query($conn, "DROP TABLE IF EXISTS [$schema].[$testTable]");
sqlsrv_query($conn, "DROP SCHEMA IF EXISTS [$schema]");
}
function readData($conn, $schema, $testTable)
{
$tsql = "SELECT id FROM [$schema].[$testTable] ORDER BY id";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
while ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
$ID = sqlsrv_get_field($stmt, 0);
echo $ID . PHP_EOL;
}
sqlsrv_free_stmt($stmt);
}
$conn = connect();
$tvpname = 'srv_id_table2';
$testTable = 'srv_test_table2';
$schema = 'srv schema';
cleanup($conn, $tvpname, $testTable, $schema);
// Create the schema
$tsql = "CREATE SCHEMA [$schema]";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
// Create the table type and test table
$tsql = "CREATE TYPE [$schema].[$tvpname] AS TABLE(id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
$tsql = "CREATE TABLE [$schema].[$testTable] (id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
// Populate the table using the table type
$tsql = "INSERT INTO [$schema].[$testTable] SELECT * FROM ?";
$params = [
[[$tvpname => [[15], [13], [11]], $schema]],
];
$stmt = sqlsrv_query($conn, $tsql, $params);
if (!$stmt) {
print_r(sqlsrv_errors());
}
sqlsrv_free_stmt($stmt);
// Verify the results
readData($conn, $schema, $testTable);
// Use Merge statement next
$tsql = <<<QRY
MERGE INTO [$schema].[$testTable] t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
$params = [
[[$tvpname => [[10], [16], [14], [12]], $schema]],
];
$stmt = sqlsrv_prepare($conn, $tsql, $params);
if (!$stmt) {
print_r(sqlsrv_errors());
}
$result = sqlsrv_execute($stmt);
if (!$result) {
print_r(sqlsrv_errors());
}
// Verify the results
readData($conn, $schema, $testTable);
cleanup($conn, $tvpname, $testTable, $schema);
echo "Done\n";
sqlsrv_close($conn);
?>
--EXPECT--
11
13
15
10
11
12
13
14
15
16
Done

View file

@ -0,0 +1,65 @@
--TEST--
GitHub issue 1310 - bind null field as varchar(max) if not binary
--DESCRIPTION--
The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102.
Note that this test does not connect with AE enabled because SQLDescribeParam() does not work with these queries.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
$conn = connect();
// Issue 1310
$query = "SELECT CAST(ISNULL(?, -1) AS INT) AS K";
$k = null;
$params = array($k);
$stmt = sqlsrv_prepare($conn, $query, $params);
if (!$stmt) {
fatalError("Failed to prepare statement (1).");
}
if (!sqlsrv_execute($stmt)) {
fatalError("Failed to execute query (1).");
}
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
var_dump($row);
}
sqlsrv_free_stmt($stmt);
// Issue 1102
$query = "DECLARE @d DATETIME = ISNULL(?, GETDATE()); SELECT @d AS D;";
$k = null;
$params = array($k, null, null, null);
$options = array('ReturnDatesAsStrings'=> true);
$stmt = sqlsrv_query($conn, $query, $params, $options);
if (!$stmt) {
fatalError("Failed to query statement (2).");
}
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
var_dump($row);
}
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
echo "Done\n";
?>
--EXPECTREGEX--
array\(1\) {
\["K"\]=>
int\(-1\)
}
array\(1\) {
\["D"\]=>
string\(23\) "20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:00.000"
}
Done

View file

@ -0,0 +1,112 @@
--TEST--
GitHub issue 1329 - string truncation error when binding some parameters as non-nulls the second time
--DESCRIPTION--
The test shows the same parameters, though bound as nulls in the first insertion, can be bound as non-nulls in the subsequent insertions.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
$connectionInfo = array('CharacterSet'=>'UTF-8');
$conn = AE\connect($connectionInfo);
dropTable($conn, 'srv_domains');
$tsql = <<<CREATESQL
CREATE TABLE srv_domains (
id bigint IDENTITY(1,1) NOT NULL,
authority nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
base_url_redirect nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
regular_not_found_redirect nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
invalid_short_url_redirect nvarchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT PK__srv_domains__3213E83F512B36BA PRIMARY KEY (id))
CREATESQL;
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
fatalError("failed to create test table");
}
$tsql = <<<INSERTSQL
INSERT INTO srv_domains (authority, base_url_redirect, regular_not_found_redirect, invalid_short_url_redirect) VALUES (?, ?, ?, ?)
INSERTSQL;
$authority = 'foo.com';
$base = null;
$notFound = null;
$invalid = null;
$params = [&$authority, &$base, &$notFound, &$invalid];
$stmt = sqlsrv_prepare($conn, $tsql, $params);
if (!$stmt) {
fatalError("failed to prepare the insert statement");
}
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("failed to execute the insert statement (1)");
}
$authority = 'detached-with-ředirects.com';
$base = 'fŏő.com';
$notFound = 'baŗ.com';
$invalid = null;
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("failed to execute the insert statement (2)");
}
$authority = 'Őther-redirects.com';
$base = 'fooš.com';
$notFound = null;
$invalid = 'ŷëå';
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("failed to execute the insert statement (3)");
}
// fetch the data
$tsql = "SELECT * FROM srv_domains";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
fatalError("failed to run select query");
}
while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC)) {
print_r($row);
}
dropTable($conn, 'srv_domains');
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
echo "Done\n";
?>
--EXPECT--
Array
(
[0] => 1
[1] => foo.com
[2] =>
[3] =>
[4] =>
)
Array
(
[0] => 2
[1] => detached-with-ředirects.com
[2] => fŏő.com
[3] => baŗ.com
[4] =>
)
Array
(
[0] => 3
[1] => Őther-redirects.com
[2] => fooš.com
[3] =>
[4] => ŷëå
)
Done