5.8.1 hotfix release (#1126)

This commit is contained in:
Jenny Tam 2020-04-15 13:49:52 -07:00 committed by GitHub
parent 7458a6c1c6
commit 5f76f838e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 4518 additions and 886 deletions

View file

@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## 5.8.1 - 2020-04-15
Updated PECL release packages. Here is the list of updates:
### Fixed
- Pull Request [#1094](https://github.com/microsoft/msphpsql/pull/1094) - Fixed default locale issues in Alpine Linux
- Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1095) - Removed unnecessary data structure to support Client-Side Cursors feature in Alpine Linux
- Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1107) - Fixed logging issues when both drivers are enabled in Alpine Linux
### 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
- 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.8.0 - 2020-01-31
Updated PECL release packages. Here is the list of updates:

View file

@ -1,7 +1,7 @@
# Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server
The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 19.10, RedHat 7 and 8, Debian 8, 9, and 10, Suse 12 and 15, Alpine 3.11 (experimental), and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup).
The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 19.10, RedHat 7 and 8, Debian 8, 9, and 10, Suse 12 and 15, Alpine 3.11, and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup).
These instructions install PHP 7.4 by default. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead.
These instructions install PHP 7.4 by default using `pecl install`. You may need to run `pecl channel-update pecl.php.net` first. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead.
Also included are instructions for installing the PHP FastCGI Process Manager, PHP-FPM, on Ubuntu. This is needed if using the nginx web server instead of Apache.
@ -293,13 +293,10 @@ To test your installation, see [Testing your installation](#testing-your-install
## Installing the drivers on Alpine 3.11
> [!NOTE]
> Alpine support is experimental.
> [!NOTE]
> The default version of PHP is 7.3. Alternate versions of PHP are not available from other repositories for Alpine 3.11. You can instead compile PHP from source.
> The default version of PHP is 7.3. Alternate versions of PHP may be available from other repositories for Alpine 3.11. You can instead compile PHP from source.
### Step 1. Install PHP
PHP packages for Alpine are found in the `edge/community` repository. Add the following line to `/etc/apt/repositories`, replacing `<mirror>` with the URL of an Alpine repository mirror:
PHP packages for Alpine can be found in the `edge/community` repository. Please check [Enable Community Repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) on their WIKI page. Add the following line to `/etc/apt/repositories`, replacing `<mirror>` with the URL of an Alpine repository mirror:
```
http://<mirror>/alpine/edge/community
```
@ -320,10 +317,7 @@ sudo su
echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/10_pdo_sqlsrv.ini
echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/00_sqlsrv.ini
```
You may need to define a locale:
```
export LC_ALL=C
```
### Step 4. Install Apache and configure driver loading
```
sudo apk add php7-apache2 apache2
@ -391,7 +385,7 @@ To test your installation, see [Testing your installation](#testing-your-install
## Testing Your Installation
To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, `/var/www/localhost/htdocs` on Alpine, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate. On Alpine 3.11, you may also need to specify the **CharacterSet** as 'UTF-8' in the `$connectionOptions` array.
To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, `/var/www/localhost/htdocs` on Alpine, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate.
```
<?php
$serverName = "yourServername";

View file

@ -25,7 +25,7 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co
[az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev
[Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev
[coveralls-site]: https://coveralls.io/github/microsoft/msphpsql?branch=dev
[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/master/graph/badge.svg
[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg
[codecov-site]: https://codecov.io/gh/microsoft/msphpsql
## Get Started

View file

@ -83,8 +83,8 @@ install:
}
- echo Downloading MSODBCSQL 17
# AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it
- ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.4.2.1_x64.msi', 'c:\projects\msodbcsql_17.4.2.1_x64.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.4.2.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL
- ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/17.5.2.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL
- echo Checking the version of MSODBCSQL
- reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server"
- dir %WINDIR%\System32\msodbcsql*.dll

View file

@ -16,7 +16,7 @@ trigger:
jobs:
- job: macOS
pool:
vmImage: 'macOS-10.13'
vmImage: 'macOS-10.14'
steps:
- checkout: self
clean: true
@ -105,7 +105,7 @@ jobs:
docker pull mcr.microsoft.com/mssql/server:2017-latest
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=$(pwd)' -p 1433:1433 -h $(host) --name=$(host) -d mcr.microsoft.com/mssql/server:2017-latest
docker ps -a
sleep 5
sleep 10
docker exec -t $(host) /opt/mssql-tools/bin/sqlcmd -S $(server) -U $(uid) -P $(pwd) -Q 'select @@Version'
displayName: 'Run SQL Server for Linux'

21
codecov.yml Normal file
View file

@ -0,0 +1,21 @@
codecov:
require_ci_to_pass: yes
max_report_age: off
coverage:
precision: 2
round: down
range: "70...100"
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "reach,diff,flags,tree"
behavior: default
require_changes: no

View file

@ -78,10 +78,15 @@ if test "$PHP_PDO_SQLSRV" != "no"; then
HOST_OS_ARCH=`uname`
if test "${HOST_OS_ARCH}" = "Darwin"; then
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion`
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion`
else
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-z,now"
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-z,now"
IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'`
IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='`
if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then
AC_DEFINE(__MUSL__, 1, [ ])
fi
fi
PHP_REQUIRE_CXX()

View file

@ -489,26 +489,13 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = {
// log a function entry point
#ifndef _WIN32
#define PDO_LOG_DBH_ENTRY \
{ \
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data ); \
driver_dbh->set_func( __FUNCTION__ ); \
int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \
char func[length+1]; \
memset(func, '\0', length+1); \
strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \
strcat_s( func, length+1, ": entering" ); \
LOG( SEV_NOTICE, func ); \
if (driver_dbh != NULL) driver_dbh->set_func(__FUNCTION__); \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}
#else
#define PDO_LOG_DBH_ENTRY \
{ \
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data ); \
driver_dbh->set_func( __FUNCTION__ ); \
LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \
}
#endif
// constructor for the internal object for connections
pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) :
@ -547,7 +534,7 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
// 0 for failure, 1 for success.
int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC)
{
LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" );
PDO_LOG_DBH_ENTRY;
hash_auto_ptr pdo_conn_options_ht;
pdo_error_mode prev_err_mode = dbh->error_mode;

View file

@ -128,11 +128,11 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
core_sqlsrv_register_logger( pdo_sqlsrv_log );
core_sqlsrv_register_severity_checker(pdo_severity_check);
REGISTER_INI_ENTRIES();
LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" );
PDO_LOG_NOTICE("pdo_sqlsrv: entering minit");
// initialize list of pdo errors
g_pdo_errors_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
@ -200,7 +200,7 @@ PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)
}
catch( ... ) {
LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" );
PDO_LOG_NOTICE("Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)");
return FAILURE;
}
@ -225,18 +225,18 @@ PHP_RINIT_FUNCTION(pdo_sqlsrv)
int set_locale = PDO_SQLSRV_G(set_locale_info);
if (set_locale == 2) {
setlocale(LC_ALL, "");
LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale LC_ALL");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_ALL");
}
else if (set_locale == 1) {
setlocale(LC_CTYPE, "");
LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale LC_CTYPE");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_CTYPE");
}
else {
LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale NONE");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale NONE");
}
#endif
LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" );
PDO_LOG_NOTICE("pdo_sqlsrv: entering rinit");
return SUCCESS;
}
@ -250,7 +250,7 @@ PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv)
SQLSRV_UNUSED( module_number );
SQLSRV_UNUSED( type );
LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" );
PDO_LOG_NOTICE("pdo_sqlsrv: entering rshutdown");
return SUCCESS;
}

View file

@ -351,26 +351,13 @@ void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op
}
// log a function entry point
#ifndef _WIN32
#define PDO_LOG_STMT_ENTRY \
{ \
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data ); \
driver_stmt->set_func( __FUNCTION__ ); \
int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \
char func[length+1]; \
memset(func, '\0', length+1); \
strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \
strcat_s( func, length+1, ": entering" ); \
LOG( SEV_NOTICE, func ); \
if (driver_stmt != NULL) driver_stmt->set_func( __FUNCTION__ ); \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}
#else
#define PDO_LOG_STMT_ENTRY \
{ \
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data ); \
driver_stmt->set_func( __FUNCTION__ ); \
LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \
}
#endif
// PDO SQLSRV statement destructor
pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void )

View file

@ -39,13 +39,6 @@ const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negati
// the warning message is not the error message alone; it must take WARNING_TEMPLATE above into consideration without the formats
const int WARNING_MIN_LENGTH = static_cast<const int>( strlen( WARNING_TEMPLATE ) - strlen( "%1!s!%2!d!%3!s!" ));
// buffer used to hold a formatted log message prior to actually logging it.
const int LOG_MSG_SIZE = 2048;
char log_msg[LOG_MSG_SIZE] = {'\0'};
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// Returns a sqlsrv_error for a given error code.
sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code);
@ -623,22 +616,10 @@ void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Ou
}
}
// Formats the error message and writes to the php error log.
void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args )
// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool pdo_severity_check(_In_ unsigned int severity TSRMLS_DC)
{
if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) {
return;
}
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args );
// if an error occurs for FormatMessage, we just output an internal error occurred.
if( rc == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg ));
std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg );
}
php_log_err( log_msg TSRMLS_CC );
return ((severity & PDO_SQLSRV_G(pdo_log_severity)));
}
namespace {

View file

@ -29,7 +29,7 @@
// request level variables
ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv)
unsigned int log_severity;
unsigned int pdo_log_severity;
zend_long client_buffer_max_size;
#ifndef _WIN32

View file

@ -60,7 +60,7 @@ extern HMODULE g_sqlsrv_hmodule;
#endif
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity,
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, pdo_log_severity,
zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong,
client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals )
@ -326,6 +326,10 @@ inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC )
}
}
#define PDO_LOG_NOTICE(message) \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, message);
#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC );
inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt )
@ -417,8 +421,8 @@ namespace pdo {
} // namespace pdo
// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro
void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args );
// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool pdo_severity_check(_In_ unsigned int severity TSRMLS_DC);
#endif /* PHP_PDO_SQLSRV_INT_H */

View file

@ -30,11 +30,6 @@
using namespace core;
// conversion matrix
// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported
// this is initialized the first time the buffered result set is created.
sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix;
namespace {
// *** internal types ***
@ -454,34 +449,6 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm
meta = static_cast<sqlsrv_buffered_result_set::meta_data*>( sqlsrv_malloc( col_count *
sizeof( sqlsrv_buffered_result_set::meta_data )));
// set up the conversion matrix if this is the first time we're called
if( conv_matrix.size() == 0 ) {
conv_matrix[SQL_C_CHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[SQL_C_CHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::system_to_wide_string;
conv_matrix[SQL_C_CHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[SQL_C_CHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::string_to_double;
conv_matrix[SQL_C_CHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::string_to_long;
conv_matrix[SQL_C_WCHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[SQL_C_WCHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[SQL_C_WCHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::wide_to_system_string;
conv_matrix[SQL_C_WCHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::wstring_to_double;
conv_matrix[SQL_C_WCHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::wstring_to_long;
conv_matrix[SQL_C_BINARY][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[SQL_C_BINARY][SQL_C_CHAR] = &sqlsrv_buffered_result_set::binary_to_system_string;
conv_matrix[SQL_C_BINARY][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::binary_to_wide_string;
conv_matrix[SQL_C_LONG][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::long_to_double;
conv_matrix[SQL_C_LONG][SQL_C_LONG] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[SQL_C_LONG][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[SQL_C_LONG][SQL_C_CHAR] = &sqlsrv_buffered_result_set::long_to_system_string;
conv_matrix[SQL_C_LONG][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::long_to_wide_string;
conv_matrix[SQL_C_DOUBLE][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[SQL_C_DOUBLE][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[SQL_C_DOUBLE][SQL_C_CHAR] = &sqlsrv_buffered_result_set::double_to_system_string;
conv_matrix[SQL_C_DOUBLE][SQL_C_LONG] = &sqlsrv_buffered_result_set::double_to_long;
conv_matrix[SQL_C_DOUBLE][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::double_to_wide_string;
}
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
stmt->encoding());
@ -844,18 +811,70 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _
*out_buffer_length = SQL_NULL_DATA;
return SQL_SUCCESS;
}
// check to make sure the conversion type is valid
conv_matrix_t::const_iterator conv_iter = conv_matrix.find( meta[field_index].c_type );
if( conv_iter == conv_matrix.end() || conv_iter->second.find( target_type ) == conv_iter->second.end() ) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0 );
return SQL_ERROR;
switch (meta[field_index].c_type) {
case SQL_C_CHAR:
switch (target_type) {
case SQL_C_CHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::system_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::string_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::string_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_WCHAR:
switch (target_type) {
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::wide_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::wstring_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::wstring_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_BINARY:
switch (target_type) {
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::binary_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::binary_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_LONG:
switch (target_type) {
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::long_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::long_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::long_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_DOUBLE:
switch (target_type) {
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::double_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::double_to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::double_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
default:
break;
}
return (( this )->*( conv_matrix[meta[field_index].c_type][target_type] ))( field_index, buffer, buffer_length,
out_buffer_length );
// Should not have reached here, return an error
last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error)))
sqlsrv_error((SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0);
return SQL_ERROR;
}
SQLRETURN sqlsrv_buffered_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,

View file

@ -287,14 +287,12 @@ struct sqlsrv_static_assert<true> { _In_ static const int value = 1; };
// Logging
//*********************************************************************************************************************************
// log_callback
// a driver specific callback for logging messages
// a driver specific callback for checking if the messages are qualified to be logged:
// severity - severity of the message: notice, warning, or error
// msg - the message to log in a FormatMessage style formatting
// print_args - args to the message
typedef void (*log_callback)( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, _In_opt_ va_list* print_args );
typedef bool (*severity_callback)(_In_ unsigned int severity TSRMLS_DC);
// each driver must register a log callback. This should be the first thing a driver does.
void core_sqlsrv_register_logger( _In_ log_callback );
// each driver must register a severity checker callback for logging to work according to the INI settings
void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker);
// a simple wrapper around a PHP error logging function.
void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ... );
@ -1746,13 +1744,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
sqlsrv_malloc_auto_ptr<SQLCHAR> temp_string; // temp buffer to hold a converted field while in use
SQLLEN temp_length; // number of bytes in the temp conversion buffer
typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t;
// two dimentional sparse matrix that holds the [from][to] functions that do conversions
static conv_matrix_t conv_matrix;
// string conversion functions
SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );

View file

@ -1799,11 +1799,11 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
case SQLSRV_PHPTYPE_INT:
{
sqlsrv_malloc_auto_ptr<long> field_value_temp;
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
sqlsrv_malloc_auto_ptr<SQLLEN> field_value_temp;
field_value_temp = static_cast<SQLLEN*>( sqlsrv_malloc( sizeof( SQLLEN )));
*field_value_temp = 0;
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( SQLLEN ),
field_len, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {

View file

@ -23,10 +23,16 @@
namespace {
severity_callback g_driver_severity;
// *** internal constants ***
log_callback g_driver_log;
// buffer used to hold a formatted log message prior to actually logging it.
const int LOG_MSG_SIZE = 2048;
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// buffer used to hold a formatted log message prior to actually logging it.
char last_err_msg[2048] = {'\0'}; // 2k to hold the error messages
@ -35,6 +41,25 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin
_In_ unsigned int mbcs_len,
_Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string,
_In_ unsigned int utf16_len, bool use_strict_conversion = false );
// invoked by write_to_log() when the message severity qualifies to be logged
// msg - the message to log in a FormatMessage style formatting
// print_args - args to the message
void log_activity(_In_opt_ const char* msg, _In_opt_ va_list* print_args)
{
char log_msg[LOG_MSG_SIZE] = { '\0' };
DWORD rc = FormatMessage(FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args);
// if an error occurs for FormatMessage, we just output an internal error occurred.
if (rc == 0) {
SQLSRV_STATIC_ASSERT(sizeof(INTERNAL_FORMAT_ERROR) < sizeof(log_msg));
std::copy(INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof(INTERNAL_FORMAT_ERROR), log_msg);
}
php_log_err(log_msg TSRMLS_CC);
}
}
// SQLSTATE for all internal errors
@ -47,22 +72,24 @@ SQLCHAR SSPWARN[] = "01SSP";
// the script (sqlsrv_configure).
void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ...)
{
SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." );
SQLSRV_ASSERT( !(g_driver_severity == NULL), "Must register a driver checker function." );
if (!g_driver_severity(severity TSRMLS_CC)) {
return;
}
va_list args;
va_start( args, msg );
g_driver_log( severity TSRMLS_CC, msg, &args );
log_activity(msg, &args);
va_end( args );
}
void core_sqlsrv_register_logger( _In_ log_callback driver_logger )
void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker)
{
g_driver_log = driver_logger;
g_driver_severity = driver_checker;
}
// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new
// length in the len parameter. If no errors occurred during convertion, true is returned and the original
// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed

View file

@ -41,6 +41,9 @@
#define CP_UTF16 1200
#define CP_ACP 0 // default to ANSI code page
bool _setLocale(const char * localeName, std::locale ** pLocale);
void setDefaultLocale(const char ** localeName, std::locale ** pLocale);
// This class provides allocation policies for the SystemLocale and AutoArray classes.
// This is primarily needed for the self-allocating ToUtf16/FromUtf16 methods.
// SNI needs all its allocations to use its own allocator so it would create a separate

View file

@ -50,38 +50,44 @@ struct cp_iconv
// CodePage 2 corresponds to binary. If the attribute PDO::SQLSRV_ENCODING_BINARY
// is set, GetIndex() above hits the assert(false) directive unless we include
// CodePage 2 below and assign an empty string to it.
#ifdef __MUSL__
#define TRANSLIT ""
#else
#define TRANSLIT "//TRANSLIT"
#endif
const cp_iconv cp_iconv::g_cp_iconv[] = {
{ 65001, "UTF-8" },
{ 1200, "UTF-16LE" },
{ 3, "UTF-8" },
{ 2, "" },
{ 1252, "CP1252//TRANSLIT" },
{ 850, "CP850//TRANSLIT" },
{ 437, "CP437//TRANSLIT" },
{ 874, "CP874//TRANSLIT" },
{ 932, "CP932//TRANSLIT" },
{ 936, "CP936//TRANSLIT" },
{ 949, "CP949//TRANSLIT" },
{ 950, "CP950//TRANSLIT" },
{ 1250, "CP1250//TRANSLIT" },
{ 1251, "CP1251//TRANSLIT" },
{ 1253, "CP1253//TRANSLIT" },
{ 1254, "CP1254//TRANSLIT" },
{ 1255, "CP1255//TRANSLIT" },
{ 1256, "CP1256//TRANSLIT" },
{ 1257, "CP1257//TRANSLIT" },
{ 1258, "CP1258//TRANSLIT" },
{ CP_ISO8859_1, "ISO8859-1//TRANSLIT" },
{ CP_ISO8859_2, "ISO8859-2//TRANSLIT" },
{ CP_ISO8859_3, "ISO8859-3//TRANSLIT" },
{ CP_ISO8859_4, "ISO8859-4//TRANSLIT" },
{ CP_ISO8859_5, "ISO8859-5//TRANSLIT" },
{ CP_ISO8859_6, "ISO8859-6//TRANSLIT" },
{ CP_ISO8859_7, "ISO8859-7//TRANSLIT" },
{ CP_ISO8859_8, "ISO8859-8//TRANSLIT" },
{ CP_ISO8859_9, "ISO8859-9//TRANSLIT" },
{ CP_ISO8859_13, "ISO8859-13//TRANSLIT" },
{ CP_ISO8859_15, "ISO8859-15//TRANSLIT" },
{ 1252, "CP1252" TRANSLIT },
{ 850, "CP850" TRANSLIT },
{ 437, "CP437" TRANSLIT },
{ 874, "CP874" TRANSLIT },
{ 932, "CP932" TRANSLIT },
{ 936, "CP936" TRANSLIT },
{ 949, "CP949" TRANSLIT },
{ 950, "CP950" TRANSLIT },
{ 1250, "CP1250" TRANSLIT },
{ 1251, "CP1251" TRANSLIT },
{ 1253, "CP1253" TRANSLIT },
{ 1254, "CP1254" TRANSLIT },
{ 1255, "CP1255" TRANSLIT },
{ 1256, "CP1256" TRANSLIT },
{ 1257, "CP1257" TRANSLIT },
{ 1258, "CP1258" TRANSLIT },
{ CP_ISO8859_1, "ISO8859-1" TRANSLIT },
{ CP_ISO8859_2, "ISO8859-2" TRANSLIT },
{ CP_ISO8859_3, "ISO8859-3" TRANSLIT },
{ CP_ISO8859_4, "ISO8859-4" TRANSLIT },
{ CP_ISO8859_5, "ISO8859-5" TRANSLIT },
{ CP_ISO8859_6, "ISO8859-6" TRANSLIT },
{ CP_ISO8859_7, "ISO8859-7" TRANSLIT },
{ CP_ISO8859_8, "ISO8859-8" TRANSLIT },
{ CP_ISO8859_9, "ISO8859-9" TRANSLIT },
{ CP_ISO8859_13, "ISO8859-13" TRANSLIT },
{ CP_ISO8859_15, "ISO8859-15" TRANSLIT },
{ 12000, "UTF-32LE" }
};
const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv);
@ -279,22 +285,46 @@ bool EncodingConverter::Initialize()
using namespace std;
#ifndef _countof
#define _countof(obj) (sizeof(obj)/sizeof(obj[0]))
#endif
const char* DEFAULT_LOCALES[] = {"en_US.UTF-8", "C"};
bool _setLocale(const char * localeName, std::locale ** pLocale)
{
try
{
*pLocale = new std::locale(localeName);
}
catch(const std::exception& e)
{
return false;
}
return true;
}
void setDefaultLocale(const char ** localeName, std::locale ** pLocale)
{
if(!localeName || !_setLocale(*localeName, pLocale))
{
int count = 0;
while(!_setLocale(DEFAULT_LOCALES[count], pLocale) && count < _countof(DEFAULT_LOCALES))
{
count++;
}
if(localeName)
*localeName = count < _countof(DEFAULT_LOCALES)?DEFAULT_LOCALES[count]:NULL;
}
}
SystemLocale::SystemLocale( const char * localeName )
: m_uAnsiCP(CP_UTF8)
, m_pLocale(NULL)
{
const char* DEFAULT_LOCALE = "en_US.UTF-8";
try {
m_pLocale = new std::locale(localeName);
}
catch(const std::exception& e) {
localeName = DEFAULT_LOCALE;
}
if(!m_pLocale) {
m_pLocale = new std::locale(localeName);
}
setDefaultLocale(&localeName, &m_pLocale);
// Mapping from locale charset to codepage
struct LocaleCP

View file

@ -27,7 +27,7 @@
// Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 8
#define SQLVERSION_PATCH 0
#define SQLVERSION_PATCH 1
#define SQLVERSION_BUILD 0
// For previews, set this constant to 1. Otherwise, set it to 0

View file

@ -44,13 +44,13 @@ if test "$PHP_SQLSRV" != "no"; then
pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/
shared_src_class=""
elif test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then
sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/
sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/
elif test -f $srcdir/shared/core_sqlsrv.h; then
sqlsrv_inc_path=$srcdir/shared/
sqlsrv_inc_path=$srcdir/shared/
else
AC_MSG_ERROR([Cannot find SQLSRV headers])
AC_MSG_ERROR([Cannot find SQLSRV headers])
fi
AC_MSG_RESULT($sqlsrv_inc_path)
AC_MSG_RESULT($sqlsrv_inc_path)
CXXFLAGS="$CXXFLAGS -std=c++11"
CXXFLAGS="$CXXFLAGS -D_FORTIFY_SOURCE=2 -O2"
@ -58,11 +58,17 @@ if test "$PHP_SQLSRV" != "no"; then
HOST_OS_ARCH=`uname`
if test "${HOST_OS_ARCH}" = "Darwin"; then
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion`
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion`
else
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-z,now"
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-z,now"
IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'`
IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='`
if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then
AC_DEFINE(__MUSL__, 1, [ ])
fi
fi
PHP_REQUIRE_CXX()
PHP_ADD_LIBRARY(stdc++, 1, SQLSRV_SHARED_LIBADD)

View file

@ -629,10 +629,9 @@ const connection_option SS_CONN_OPTS[] = {
PHP_FUNCTION ( sqlsrv_connect )
{
LOG_FUNCTION( "sqlsrv_connect" );
SET_FUNCTION_NAME( *g_ss_henv_cp );
SET_FUNCTION_NAME( *g_ss_henv_ncp );
g_ss_henv_cp->set_func(_FN_);
g_ss_henv_ncp->set_func(_FN_);
reset_errors( TSRMLS_C );
@ -785,7 +784,7 @@ PHP_FUNCTION( sqlsrv_close )
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
error_ctx->set_func(_FN_);
if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) {
@ -816,7 +815,7 @@ PHP_FUNCTION( sqlsrv_close )
throw ss::SSException();
}
SET_FUNCTION_NAME( *conn );
conn->set_func(_FN_);
// cause any variables still holding a reference to this to be invalid so they cause
// an error when passed to a sqlsrv function. There's nothing we can do if the
@ -845,13 +844,15 @@ PHP_FUNCTION( sqlsrv_close )
void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC )
{
LOG_FUNCTION( "sqlsrv_conn_dtor" );
// Without sqlsrv_close(), this function is invoked by php during the final clean up stage.
// To prevent memory/resource leaks, no more logging at this point.
//LOG_FUNCTION( "sqlsrv_conn_dtor" );
// get the structure
ss_sqlsrv_conn *conn = static_cast<ss_sqlsrv_conn*>( rsrc->ptr );
SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null");
SET_FUNCTION_NAME( *conn );
conn->set_func(__func__);
// close all statements associated with the connection.
sqlsrv_conn_close_stmts( conn TSRMLS_CC );

View file

@ -271,8 +271,8 @@ PHP_MINIT_FUNCTION(sqlsrv)
{
SQLSRV_UNUSED( type );
core_sqlsrv_register_logger( ss_sqlsrv_log );
core_sqlsrv_register_severity_checker(ss_severity_check);
// our global variables are initialized in the RINIT function
#if defined(ZTS)
if( ts_allocate_id( &sqlsrv_globals_id,

View file

@ -313,15 +313,11 @@ public:
#define LOG_FUNCTION( function_name ) \
const char* _FN_ = function_name; \
SQLSRV_G( current_subsystem ) = current_log_subsystem; \
LOG( SEV_NOTICE, "%1!s!: entering", _FN_ );
core_sqlsrv_register_severity_checker(ss_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", _FN_);
#define SET_FUNCTION_NAME( context ) \
{ \
(context).set_func( _FN_ ); \
}
// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro
void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args );
// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool ss_severity_check(_In_ unsigned int severity TSRMLS_DC);
// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise.
enum logging_subsystems {

View file

@ -1371,7 +1371,6 @@ void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC )
PHP_FUNCTION( sqlsrv_free_stmt )
{
LOG_FUNCTION( "sqlsrv_free_stmt" );
zval* stmt_r = NULL;
@ -1384,7 +1383,7 @@ PHP_FUNCTION( sqlsrv_free_stmt )
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
error_ctx->set_func(_FN_);
// take only the statement resource
if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) {

View file

@ -30,13 +30,6 @@ namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_UTIL;
// buffer used to hold a formatted log message prior to actually logging it.
const int LOG_MSG_SIZE = 2048;
char log_msg[LOG_MSG_SIZE] = {'\0'};
// internal error that says that FormatMessage failed
SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
// *** internal functions ***
sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code );
@ -457,21 +450,10 @@ ss_error SS_ERRORS[] = {
{ UINT_MAX, {} }
};
// Formats an error message and finally writes it to the php log.
void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args )
// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool ss_severity_check(_In_ unsigned int severity TSRMLS_DC)
{
if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) {
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args );
// if an error occurs for FormatMessage, we just output an internal error occurred.
if( rc == 0 ) {
SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg ));
std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg );
}
php_log_err( log_msg TSRMLS_CC );
}
return ((severity & SQLSRV_G(log_severity)) && (SQLSRV_G(current_subsystem) & SQLSRV_G(log_subsystems)));
}
bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args )
@ -598,7 +580,7 @@ PHP_FUNCTION( sqlsrv_configure )
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
error_ctx->set_func(_FN_);
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
@ -718,7 +700,7 @@ PHP_FUNCTION( sqlsrv_get_config )
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
SET_FUNCTION_NAME( *error_ctx );
error_ctx->set_func(_FN_);
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {

View file

@ -1,15 +1,18 @@
<?php
require_once('connect.inc');
// Set SQL server + user + password
$serverName = getenv('MSSQL_SERVERNAME') ?: "sql-2k14-sp2-1.galaxy.ad";
$username = getenv('MSSQL_USERNAME') ?: "sa";
$password = getenv('MSSQL_PASSWORD') ?: "Moonshine4me";
$serverName = getenv('MSSQL_SERVERNAME') ?: $server2;
$username = getenv('MSSQL_USERNAME') ?: $uid;
$password = getenv('MSSQL_PASSWORD') ?: $pwd;
// Generate unique DB name, example: php_20160817_1471475608267
$databaseName = "php_" . date("Ymd") . "_" . round(microtime(true)*1000);
// Generic table name example: php_20160817_1471475608267.dbo.php_firefly
$tableName1 = $databaseName.".dbo.php_firefly1";
$tableName2 = $databaseName.".dbo.php_firefly2";
// Generic table name example: php_20160817_1471475608267.dbo.php_table
$tableName1 = $databaseName.".dbo.php_table1";
$tableName2 = $databaseName.".dbo.php_table2";
$connectionInfo = array( "Database"=>"$databaseName", "username"=>"$username", "password"=>"$password" );
$conn = sqlsrv_connect( $serverName, $connectionInfo );

View file

@ -4,6 +4,8 @@ $databaseName = 'TARGET_DATABASE';
$uid = 'TARGET_USERNAME';
$pwd = 'TARGET_PASSWORD';
$server2 = 'ANOTHER_SERVER';
// RevisionNumber in SalesOrderHeader is subject to a trigger incrementing it whenever
// changes are made to SalesOrderDetail. Since RevisionNumber is a tinyint, it can
// overflow quickly if the BVT tests often run. So we change it directly here first

View file

@ -10,8 +10,9 @@ $conn = sqlsrv_connect( $server, $connectionInfo);
/* Connect to the local server using Windows Authentication and
specify the AdventureWorks database as the database in use. */
$serverName = "sql-2k14-sp1-1.galaxy.ad";
$connectionInfo = array( "Database"=>"AdventureWorks2014", "UID"=>"sa", "PWD"=>"Moonshine4me", 'MultipleActiveResultSets'=> false);
$serverName = $server2;
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, 'MultipleActiveResultSets'=> false);
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn === false )
{

View file

@ -1,8 +1,8 @@
<?php
/* Connect to the local server using Windows Authentication and
specify the AdventureWorks database as the database in use. */
$serverName = "sql-2k14-sp1-1.galaxy.ad";
$connectionInfo = array( "Database"=>"AdventureWorks2014", "UID"=>"sa", "PWD"=>"Moonshine4me");
$serverName = $server2;
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd);
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn === false )
{

View file

@ -4,11 +4,8 @@ server side cursor specified when querying
--FILE--
<?php
//$server = "sql-2k14-sp1-1.galaxy.ad";
//$conn = sqlsrv_connect( $server, array( "Database" => "tempdb", "UID"=>"sa", "PWD"=>"Moonshine4me"));
require('connect.inc');
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd);
$conn = sqlsrv_connect( $server, $connectionInfo);
if ( $conn === false ) {
die( print_r( sqlsrv_errors(), true ));

View file

@ -0,0 +1,220 @@
<?php
include("MsSetup.inc");
$tableName = "aev2test";
// Array of possible ColumnEncryption values. $ceValues is for the initial
// connection, $targetCeValues is for reconnection. The array keys are to be
// passed to testCompare and testPatternMatch so we don't have to pass the
// actual attestation info, which resides in MsSetup.inc.
// For incorrect protocol and attestation URL, insert a rogue 'x' on either side
// of the comma.
$comma = strpos($attestation, ',');
$wrongProtocol = substr_replace($attestation, 'x', $comma, 0);
$wrongAttestation = substr_replace($attestation, 'x', $comma+1, 0);
$ceValues = array('correct' =>$attestation,
'enabled' =>'enabled',
'disabled'=>'disabled',
'invalid' =>$wrongProtocol,
'wrongurl'=>$wrongAttestation,
);
$targetCeValues = array('correct' =>$attestation,
'enabled' =>'enabled',
'disabled'=>'disabled',
'invalid' =>$wrongProtocol,
'wrongurl'=>$wrongAttestation,
);
// Names of the encryption keys, depending on whether we are using Windows
// or AKV authentication (defined in MsSetup.inc). -enclave keys are enclave
// enabled, -noenclave keys are not enclave enabled.
// $targetKeys are the keys used for re-encrypting encrypted columns
if ($keystore == 'win') {
$keys = array("CEK-win-enclave",
"CEK-win-noenclave"
);
$targetKeys = array("CEK-win-enclave",
"CEK-win-noenclave",
"CEK-win-enclave2",
"CEK-win-noenclave2"
);
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave",
"CEK-akv-noenclave"
);
$targetKeys = array("CEK-akv-enclave",
"CEK-akv-noenclave",
"CEK-akv-enclave2",
"CEK-akv-noenclave2"
);
} else {
die("No keystore specified! Aborting...\n");
}
// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Deterministic",
"Randomized",
);
$targetTypes = array("Deterministic",
"Randomized",
);
// Length of the string-type columns. $slength is length as a string instead of integer
$length = 64;
$slength = '64';
// Testing the following data types, split into two arrays because if we try one array,
// at some point we get a CE405 error for no clear reason (might be a memory issue?).
// TODO: Follow up and see if we can use a single type array.
$dataTypes1 = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
'smalldatetime',
);
$dataTypes2 = array('char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
'binary',
'varbinary',
'varbinary(max)',
);
// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames1 = array();
$colNamesAE1 = array();
$colNames2 = array();
$colNamesAE2 = array();
foreach ($dataTypes1 as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames1[$type] = "c_".$column;
$colNamesAE1[$type] = "c_".$column."_AE";
}
foreach ($dataTypes2 as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames2[$type] = "c_".$column;
$colNamesAE2[$type] = "c_".$column."_AE";
}
// The test data below is a mixture of random data and edge cases
$testValues = array();
// integers
$testValues['integer'] = array(0,-1,1,2147483647,-2147483648,65536,-100000000,128,9);
$testValues['bigint'] = array(9223372036854775807,-40,0,1,2147483647,-2147483648,65536,-100000000000000);
$testValues['smallint'] = array(4,-4,-32768,-99,32767,-30000,-12,-1);
$testValues['tinyint'] = array(2,0,255,254,99,101,100,32);
$testValues['bit'] = array(1,1,0,0,0,0,1,0);
// floating point
$testValues['float'] = array(3.14159,2.3e+12,-2.3e+12,2.23e-308,1,-1.79e+308,892.3098234234001,1.2);
$testValues['real'] = array(3.14159,2.3e+12,-2.3e+12,1.18e-38,1,-3.4e+38,892.3098234234001,1.2);
$testValues['numeric'] = array(-3.14159,1.003456789,45.6789,0.0001,987987.12345,-987987.12345,100000000000,-100000000000);
// dates and times
$testValues['date'] = array('2010-01-31','0485-03-31','7825-07-23','9999-12-31','1956-02-27','2018-09-01','5401-11-02','1031-10-04');
$testValues['time'] = array('12:40:40','08:14:54.3096','23:59:59.9999','01:00:34.0101','21:45:45.4545','00:23:45.6','17:48:00.0000','20:31:49.0001');
$testValues['datetime2'] = array('9801-01-29 11:45:23.5092856','2384-12-31 12:40:12.5434323','1984-09-25 10:40:20.0909111','9999-12-31 23:59:59.999999',
'1259-04-29 23:59:59.9999999','1748-09-21 17:48:54.723','3125-05-31 05:00:32.4','0001-01-01 00:00:00');
$testValues['datetimeoffset'] = array('9801-01-29 11:45:23.5092856-12:45','0001-01-01 00:00:00-02:30','1984-09-25 10:40:20.0909111+03:00','1748-09-21 17:48:54.723-09:21',
'4896-05-18 23:17:58.3-02:00','1657-08-04 18:14:27.4','2022-03-17 07:31:45.890342+09:30','1987-10-25 14:27:34.6320945-06:00');
$testValues['datetime'] = array('9801-01-29 11:45:23.509','2384-12-31 12:40:12.543','1984-09-25 10:40:20.090','9999-12-31 23:59:59.997',
'2753-04-29 23:59:59.997','1948-09-21 17:48:54.723','3125-05-31 05:00:32.4','2001-01-01 00:00:00');
$testValues['smalldatetime'] = array('1998-06-13 04:00:00','1985-03-31 12:40:00','2025-07-23 05:00:00','1999-12-31 00:00:00',
'1956-02-27 23:59:00','2018-09-01 14:35:00','2079-06-06 23:59:00','1931-10-04 19:52:00');
// strings, ascii and unicode
$testValues['char'] = array('wvyxz', 'tposw', '%c@kj>5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@');
$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','ኁ㵮ഖᅥ㪮ኸ⮊ߒᙵꇕ⯐គꉟफ़⻦ꈔꇼŞ','ꐷꬕ','㐯㩧㖃⺵㴰ڇལᧆ겴ꕕ겑וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪぅᾔᎀ㍏겶ꅫၞ㴉ᴳ㜞҂','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬ㐯ꋛ㗾');
$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?');
$testValues['nvarchar'] = array('ᾁẴ㔮㖖ୱܝ㐗㴴៸ழ᷂ᵄ葉អ㺓節','ӕᏵ൴ꔓὀ⾼','Ὡ','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','ꁈ ݫ','ꍆફⷌ㏽̗ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮᏏᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲ更ꧥ');
$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z',
'9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nq<i-Nqbu@]1<K{PS[SHSF(]G[G XPLlAUezBm^&qn^mK(&]ss6&yVxW_N_J5V*iKcgXyb+Hz:HS<9>rc1%n@|N|ik C@ 03a/ +H9mBq',
'SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l<e`N@');
$testValues['nvarchar(max)'] = array('xᐕᛙᘡ','ퟅ㚶Ἢœäᑐï','ꐾɔᡧ㝚ஒŪᚔᘘښ곅սꕟքꀉᎠኇ','t9p4r5', '﹨ퟱꈽᕧු꧍ۡᢙⴖ㒘ᆾ겇ᅞ〱㝸㛾㕥গଜ㳸ꍍ匿ཋ㵔ﬠᄩ᧙ꖍᕿ㩴ఽᙿ','ⴠ⿃ᶺ͚ᎉ઄㵨጑㛢㌋㙤ᙘّᘷ㬡',
'ᵄご︵ࣲꌤꏵퟰꖛᏠƢᵙꌵ㙈㜂琢㎯㪏㐵꒚㧶ᐁቴƯɋü㶌領㻡㉉걂ꈊㇷѼμꅲڧᶀƸڍ⭩㉩㛜ꆶ㕸ꁺꖁ㓫ޘ갧ᛄ㶋㘚ᐋꗡͭచ㖔፟ꐸ㱯ⵜᥰꃷᇂὥ㗍㚀ꀊጿἢઔܛ᎓Ե⅜㛵጑ྣᏝ༱⮢ΫÊ㕮⽹','繁Ɇʓӿꩭਸꆟꑇ㳋Ήᴝ㕨㰵ꇳ');
// binary
$testValues['binary'] = array('3AD2BBC2', '7201', 'EED3A109F8F7745C', 'D6C3E0E11A25F3','4EACCEF38788F9','FFFFFFFFFFFFFFFF', '6230974598','44EE4A');
$testValues['varbinary'] = array('583412','F3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC8889','E43004FF', '000005ED309D3A45','06CADEF379','8041','00000000000000','D7209FFE44');
$testValues['varbinary(max)'] = array('EF409CB33408', 'D3EA762C78FC', '00','FFFFFFFFFFFFFFFFFFFFFFFFFF',
'582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1FE',
'92300943891097','DDD4D88C4B80089D2E4A','88F8A8');
// The comparison operators to test
$comparisons = array('=', '<', '>', '<=', '>=', '<>', '!<', '!>');
// Thresholds against which to use the comparison operators
$thresholds = array('integer' => 0,
'bigint' => 0,
'smallint' => 1000,
'tinyint' => 100,
'bit' => 0,
'float' => 1.2,
'real' => -1.2,
'numeric' => 45.6789,
'char' => 'rstuv',
'nchar' => '㊃ᾞਲ㨴꧶ꁚꅍ',
'varchar' => '6$gt?je#~',
'nvarchar' => 'ӕᏵ൴ꔓὀ⾼',
'varchar(max)' => 'hijkl',
'nvarchar(max)' => 'xᐕᛙᘡ',
'binary' => '44EE4A',
'varbinary' => 'E43004FF',
'varbinary(max)' => 'D3EA762C78FC',
'date' => '2010-01-31',
'time' => '21:45:45.4545',
'datetime' => '3125-05-31 05:00:32.4',
'datetime2' => '2384-12-31 12:40:12.5434323',
'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00',
'smalldatetime' => '1998-06-13 04:00:00',
);
// String patterns to test with LIKE
// For AE, LIKE only works with string types for now. Additional types
// are listed here because eventually the type conversions required for
// pattern matching non-string types should be supported.
$patterns = array('integer' => array('8', '48', '123'),
'bigint' => array('000','7', '65536'),
'smallint' => array('4','768','abc'),
'tinyint' => array('9','0','25'),
'bit' => array('0','1','100'),
'float' => array('14159','.','E+','2.3','308'),
'real' => array('30','.','e-','2.3','38'),
'numeric' => array('0','0000','12345','abc','.'),
'char' => array('w','@','x*fv=u$','e3'),
'nchar' => array('af㋮','㐯ꋛ㗾','ꦣ㧼ለͭ','123'),
'varchar' => array(' ','a','#','@@)'),
'nvarchar' => array(' ','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','更ꧥ','ꈔꇼŞ'),
'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'),
'nvarchar(max)' => array('t','㧶ᐁቴƯɋ','ᘷ㬡',' ','ꐾɔᡧ㝚'),
'binary' => array('44EE4A'),
'varbinary' => array('E43004FF'),
'varbinary(max)' => array('D3EA762C78FC'),
'date' => array('20','%','9-','04'),
'time' => array('4545','.0','20:','12345',':'),
'datetime' => array('997','12',':5','9999'),
'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'),
'datetimeoffset' => array('+02','96',' ','5092856',':00'),
'smalldatetime' => array('00','1999','abc',':','06'),
);
?>

58
test/extended/MsSetup.inc Normal file
View file

@ -0,0 +1,58 @@
<?php
/*
Microsoft SQL Server Driver for PHP - Unit Test Framework
Copyright (c) Microsoft Corporation. All rights reserved.
Description:
Global variables defining the execution context
*/
$PhpDriver = "Microsoft SQL Server Driver for PHP";
$server = 'TARGET_SERVER';
$database = 'TARGET_DATABASE';
$userName = 'TARGET_USERNAME';
$userPassword = 'TARGET_PASSWORD';
$tableName = "php_test_table";
$tableIndex = "php_test_table_index";
$procName = "php_test_proc";
$fileName = "php_test_file.dat";
$driver = "ODBC Driver 17 for SQL Server";
$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, "TraceOn" => false, "Driver" => $driver);
$daasMode = false;
$marsMode = true;
$traceEnabled = false;
$adServer = 'TARGET_AD_SERVER';
$adDatabase = 'TARGET_AD_DATABASE';
$adUser = 'TARGET_AD_USERNAME';
$adPassword = 'TARGET_AD_PASSWORD';
if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['MSSQL_PASSWORD'])) {
$server = $_ENV['MSSQL_SERVER'];
$uid = $_ENV['MSSQL_USER'];
$pwd = $_ENV['MSSQL_PASSWORD'];
$databaseName = $_ENV['MSSQL_DATABASE_NAME'];
} else {
$uid = $userName;
$pwd = $userPassword;
$databaseName = $database;
}
// column encryption variables
$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv
$dataEncrypted = false; // whether data is to be encrypted
// for Azure Key Vault
$AKVKeyStoreAuthentication = 'TARGET_AKV_AUTH'; // can be KeyVaultPassword or KeyVaultClientSecret
$AKVPrincipalName = 'TARGET_AKV_PRINCIPAL_NAME'; // for use with KeyVaultPassword
$AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPassword
$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret
$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret
// for enclave computations
$attestation = 'TARGET_ATTESTATION';
?>

View file

@ -0,0 +1,911 @@
<?php
// runPlaintextTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place plaintext encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->query("DBCC FREEPROCCACHE");
// Create and populate a non-encrypted table
$createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
try {
$stmt = $conn->query("DROP TABLE IF EXISTS $tableName");
$stmt = $conn->query($createQuery);
} catch(Exception $error) {
print_r($error);
die("Creating a plaintext table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
// Encrypt the table
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitdataTypes = array_chunk($dataTypes, 5);
foreach ($splitdataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength);
$isEncrypted = encryptTable($conn, $alterQuery, $key, $encryptionType, $attestationType);
}
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitdataTypes = array_chunk($dataTypes, 5);
foreach ($splitdataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// runEncryptedTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place re-encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
// Cannot create a table with encrypted data if CE is disabled
// TODO: Since we can create an empty encrypted table with
// CE disabled, account for the case where CE is disabled.
if ($ceValue == 'disabled') continue;
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->query("DBCC FREEPROCCACHE");
// Create and populate an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
try {
$stmt = $conn->query("DROP TABLE IF EXISTS $tableName");
$stmt = $conn->query($createQuery);
} catch(Exception $error) {
print_r($error);
die("Creating an encrypted table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
$ceDisabled = ($attestationType == 'disabled') ? true : false;
insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled);
$isEncrypted = true;
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
$initiallyEnclaveEncryption = isEnclaveEnabled($key);
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitdataTypes = array_chunk($dataTypes, 5);
foreach ($splitdataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType, true, $initiallyEnclaveEncryption);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// Connect and clear the procedure cache
function connect($attestationInfo)
{
require("MsSetup.inc");
$options = "sqlsrv:Server=$server;Database=$databaseName;ColumnEncryption=$attestationInfo";
if ($keystore == 'akv') {
$securityInfo = '';
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$securityInfo .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$securityInfo .= ";KeyStorePrincipalId=$AKVPrincipalName";
$securityInfo .= ";KeyStoreSecret=$AKVPassword";
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$securityInfo .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$securityInfo .= ";KeyStorePrincipalId=$AKVClientID";
$securityInfo .= ";KeyStoreSecret=$AKVSecret";
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options .= $securityInfo;
}
try {
$conn = new PDO($options, $uid, $pwd);
} catch (PDOException $error) {
$e = $error->errorInfo;
checkErrors($e, array('CE400', '0'));
return false;
}
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
// Check that enclave computations are enabled
// See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave
$query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';";
$stmt = $conn->query($query);
if (!$stmt) {
print_r($conn->errorInfo());
die("Error when checking if enclave computations are enabled. This should never happen! Non-HGS servers should have been skipped.\n");
} else {
$info = $stmt->fetch();
if (empty($info) or ($info['value'] != 1) or ($info['value_in_use'] != 1)) {
die("Error: enclave computations are not enabled on the server!");
}
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->exec("DBCC FREEPROCCACHE");
unset($stmt);
return $conn;
}
// This CREATE TABLE query simply creates a non-encrypted table with
// two columns for each data type side by side
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer,
// c_integer_AE integer
// )
function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength."), \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n ";
} else {
$query = $query.$colNames[$type]." ".$type.", \n ";
$query = $query.$colNamesAE[$type]." ".$type.", \n ";
}
}
// Remove the ", \n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -7)."\n)";
return $query;
}
// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must
// be preceded by ALTER TABLE
// This produces a query that looks like
// ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_integer_AE] integer
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_bigint_AE] bigint
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength)
{
$query = '';
foreach ($dataTypes as $dataType) {
$plength = dataTypeIsString($dataType) ? "(".$slength.")" : "";
$collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : "";
$query = $query." ALTER TABLE [dbo].[".$tableName."]
ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate."
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH
(ONLINE = ON);";
}
$query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;";
return $query;
}
// This CREATE TABLE query creates a table with two columns for
// each data type side by side, one plaintext and one encrypted
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer NULL,
// c_integer_AE integer
// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
// )
function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
$collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : "";
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
} else {
$query = $query.$colNames[$type]." ".$type." NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type." \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
}
}
// Remove the ",\n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -6)."\n)";
return $query;
}
// The INSERT query for the table
function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE)
{
$queryTypes = "(";
$valuesString = "VALUES (";
foreach ($dataTypes as $type) {
$colName1 = $colNames[$type].", ";
$colName2 = $colNamesAE[$type].", ";
$queryTypes .= $colName1;
$queryTypes .= $colName2;
$valuesString .= "?, ?, ";
}
// Remove the ", " from the end of the query or the comma will cause a syntax error
$queryTypes = substr($queryTypes, 0, -2).")";
$valuesString = substr($valuesString, 0, -2).")";
$insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString;
return $insertQuery;
}
function insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled=false)
{
if (empty($testValues)) {
die("$testValues is empty or non-existent. Please check the required values file.\n");
}
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
// Insert the data using PDO::prepare()
try {
$stmt = $conn->prepare($insertQuery);
$i=1;
foreach ($dataTypes as $type) {
$PDOType = getPDOType($type);
if (!dataTypeIsBinary($type)) {
$stmt->bindParam($i, $testValues[$type][$v], $PDOType);
$stmt->bindParam($i+1, $testValues[$type][$v], $PDOType);
} else {
// unset() is necessary because otherwise the same data may be
// inserted into multiple binary columns.
unset($val);
$val=pack('H*', $testValues[$type][$v]);
$stmt->bindParam($i, $val, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
$stmt->bindParam($i+1, $val, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
}
$i+=2;
}
$stmt->execute();
} catch (PDOException $error) {
if (!$ceDisabled) {
print_r($error);
die("Inserting values in encrypted table failed\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('22018', '206'));
}
}
}
unset($stmt);
}
// encryptTable attempts to encrypt the table in place and verifies
// if it works given the attestation info and key type.
// Arguments:
// resource $conn: The connection
// string $alterQuery: The query to encrypt the table
// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl'
// bool $sameKeyAndType: Whether the key and encryption type are same for re-encrypting
// as for initial encryption.
// bool $initialEncryption: Whether we are testing with table initially encrypted, instead
// of plaintext being encrypted after creation
// bool $initiallyEnclaveEncrypted: Whether the table was initally encrypted with an
// enclave-enabled key
function encryptTable($conn, $alterQuery, $key, $encryptionType, $attestation, $sameKeyAndType=false, $initialEncryption=false, $initallyEnclaveEncrypted=false)
{
try {
$stmt = $conn->query($alterQuery);
if ((!isEnclaveEnabled($key) or $attestation != 'correct') and !$sameKeyAndType) {
die("Encrypting should have failed with attestation $attestation, key $key and encryption type $encryptionType\n");
}
} catch (PDOException $error) {
if ($sameKeyAndType) {
print_r($error);
die("Encrypting table should not fail when target encryption key and type are the same as source: attestation $attestation, key $key and encryption type $encryptionType\n");
} elseif ($initialEncryption and !$initallyEnclaveEncrypted) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
} elseif ($attestation == 'correct') {
if (isEnclaveEnabled($key)) {
print_r($error);
die("Encrypting with correct attestation failed when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'enabled' or $attestation == 'disabled') {
if (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33546'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'wrongurl') {
if (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('CE405', '0'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'invalid') {
die("Encrypting table with invalid protocol! Should not get here!\n");
} else {
die("Error! This is no-man's-land\n");
}
return false;
}
unset($stmt);
return true;
}
// compareResults checks that the results between the encrypted and non-encrypted
// columns are identical if statement execution succeeds. If statement execution
// fails, this function checks for the correct error.
// Arguments:
// statement $AEstmt: Prepared statement fetching encrypted data
// statement $nonAEstmt: Prepared statement fetching non-encrypted data
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
// string $comparison: Comparison operator
// string $type: Data type the comparison is operating on
function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison='', $type='')
{
try {
$nonAEstmt->execute();
} catch(Exception $error) {
print_r($error);
die("Executing non-AE computation statement failed!\n");
}
try {
$AEstmt->execute();
} catch(Exception $error) {
if (!$isEncrypted) {
die("Computation statement execution should not have failed for an unencrypted table: attestation $attestation, key $key and encryption type $encryptionType\n");
}
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r($error);
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33546'));
} elseif (!isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} else {
print_r($error);
die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType");
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
$e = $error->errorInfo;
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('CE405', '0'));
} elseif (!isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} else {
print_r($error);
die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType");
}
} elseif ($attestation == 'correct') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} elseif ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r($error);
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} else {
print_r($error);
die("Comparison failed for correct attestation when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n");
}
} elseif ($attestation == 'disabled') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} elseif ($comparison == '=' or $comparison == '<>' or $encryptionType == 'Randomized') {
$e = $error->errorInfo;
checkErrors($e, array('22018', '206'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} else {
print_r($error);
die("Unexpected error occurred in compareResults: attestation $attestation, key $key and encryption type $encryptionType\n");
}
return;
}
$AEres = $AEstmt->fetchAll(PDO::FETCH_NUM);
$nonAEres = $nonAEstmt->fetchAll(PDO::FETCH_NUM);
$AEcount = count($AEres);
$nonAEcount = count($nonAEres);
if ($type == 'char' or $type == 'nchar' or $type == 'binary') {
// char and nchar may not return the same results - at this point
// we've verified that statement execution works so just return
// TODO: Check if this bug is fixed and if so, remove this if block
return;
} elseif ($AEcount > $nonAEcount) {
print_r("Too many AE results for operation $comparison and data type $type!\n");
print_r($AEres);
print_r($nonAEres);
} elseif ($AEcount < $nonAEcount) {
print_r("Too many non-AE results for operation $comparison and data type $type!\n");
print_r($AEres);
print_r($nonAEres);
} else {
if ($AEcount != 0) {
$i = 0;
foreach ($AEres as $AEr) {
if ($AEr[0] != $nonAEres[$i][0]) {
print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i][0]." and non-AE result ".$nonAEres[$i][0]."\n");
}
++$i;
}
}
}
}
// testCompare selects based on a comparison in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Table name
// array $comparisons: Comparison operations from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc
// string $key: Name of the encryption key
// integer $length: Length of the string types, from AE_v2_values.inc
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
// bool $isEncrypted: Whether the table is encrypted
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation, $isEncrypted)
{
foreach ($comparisons as $comparison) {
foreach ($dataTypes as $type) {
// Unicode operations with AE require the Latin1_General_BIN2
// collation. If the COLLATE clause is left out, we get different
// results between the encrypted and non-encrypted columns (probably
// because the collation was only changed in the encryption query).
$string = dataTypeIsStringMax($type);
$collate = $string ? " COLLATE Latin1_General_BIN2" : "";
$unicode = dataTypeIsUnicode($type);
$PDOType = getPDOType($type);
unset($threshold);
$threshold = dataTypeIsBinary($type) ? pack('H*', $thresholds[$type]) : $thresholds[$type];
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate;
try {
$AEstmt = $conn->prepare($AEQuery);
$nonAEstmt = $conn->prepare($nonAEQuery);
if (!dataTypeIsBinary($type)) {
$AEstmt->bindParam(1, $threshold, $PDOType);
$nonAEstmt->bindParam(1, $threshold, $PDOType);
} else {
$AEstmt->bindParam(1, $threshold, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
$nonAEstmt->bindParam(1, $threshold, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
}
} catch (PDOException $error) {
print_r($error);
die("Preparing/binding statements for comparison failed! Comparison $comparison, type $type");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
// testPatternMatch selects based on a pattern in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Table name
// array $patterns: Strings to pattern match, from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl'
// bool $isEncrypted: Whether the table is encrypted
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation, $isEncrypted)
{
foreach ($dataTypes as $type) {
// TODO: Pattern matching doesn't work in AE for non-string types.
// This is for security reasons, follow up on it.
if (!dataTypeIsStringMax($type)) {
continue;
}
foreach ($patterns[$type] as $pattern) {
$patternArray = array($pattern,
$pattern."%",
"%".$pattern,
"%".$pattern."%",
);
foreach ($patternArray as $spattern) {
// Unicode operations with AE require the PHPTYPE to be specified as
// UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE
// clause is left out, we get different results between the
// encrypted and non-encrypted columns (probably because the
// collation was only changed in the encryption query).
// We must pass the length of the pattern matching string
// to the SQLTYPE instead of the field size, as we usually would,
// because otherwise we would get an empty result set.
// We need iconv_strlen to return the number of characters
// for unicode strings, since strlen returns the number of bytes.
$unicode = dataTypeIsUnicode($type);
$collate = $unicode ? " COLLATE Latin1_General_BIN2" : "";
$PDOType = getPDOType($type);
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate;
// TODO: Add binary type support below. May need to use unset()
// as in insertValues().
try {
$AEstmt = $conn->prepare($AEQuery);
$AEstmt->bindParam(1, $spattern, $PDOType);
$nonAEstmt = $conn->prepare($nonAEQuery);
$nonAEstmt->bindParam(1, $spattern, $PDOType);
} catch (PDOException $error) {
print_r($error);
die("Preparing/binding statements for comparison failed! Comparison $comparison, type $type\n");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $pattern, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
}
// Check that the expected errors ($codes) is found in the PDOException ($errors)
function checkErrors($errors, ...$codes)
{
$codeFound = false;
foreach ($codes as $code) {
if ($code[0]==$errors[0] and $code[1]==$errors[1]) {
$codeFound = true;
break;
}
}
if ($codeFound == false) {
echo "Error: ";
print_r($errors);
echo "\nExpected: ";
print_r($codes);
echo "\n";
die("Error code not found.\n");
}
}
function isEnclaveEnabled($key)
{
return (strpos($key, '-enclave') !== false);
}
function dataTypeIsString($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"]));
}
function dataTypeIsStringMax($dataType)
{
return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeNeedsCollate($dataType)
{
return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeIsUnicode($dataType)
{
return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"]));
}
function dataTypeIsBinary($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "varbinary(max)"]));
}
function getPDOType($type)
{
switch($type) {
case "bigint":
case "integer":
case "smallint":
case "tinyint":
return PDO::PARAM_INT;
case "bit":
return PDO::PARAM_BOOL;
case "real":
case "float":
case "double":
case "numeric":
case "time":
case "date":
case "datetime2":
case "datetime":
case "datetimeoffset":
case "smalldatetime":
case "money":
case "smallmoney";
case "xml":
case "uniqueidentifier":
case "char":
case "varchar":
case "varchar(max)":
case "nchar":
case "nvarchar":
case "nvarchar(max)":
return PDO::PARAM_STR;
case "binary":
case "varbinary":
case "varbinary(max)":
return PDO::PARAM_LOB;
default:
die("Case is missing for $type type in getPDOType.\n");
}
}
?>

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating a
plaintext table each time, then trying to encrypt it with different combinations
of enclave-enabled and non-enclave keys and encryption types. It then reconnects
and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting
the table with different target combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create a table in plaintext with two columns for each AE-supported data type.
2. Insert some data in plaintext.
3. Encrypt one column for each data type.
4. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
5. Ensure the two results are the same.
6. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating a
plaintext table each time, then trying to encrypt it with different combinations
of enclave-enabled and non-enclave keys and encryption types. It then reconnects
and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting
the table with different target combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create a table in plaintext with two columns for each AE-supported data type.
2. Insert some data in plaintext.
3. Encrypt one column for each data type.
4. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
5. Ensure the two results are the same.
6. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $targetTypes,
and $targetKeys to try re-encrypting the table with different combinations of
enclave-enabled and non-enclave keys and encryption types.
The sequence of operations is the following:
1. Create an encrypted table with two columns for each AE-supported data type,
one encrypted and one not encrypted.
2. Insert some data.
3. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
4. Ensure the two results are the same.
5. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $targetTypes,
and $targetKeys to try re-encrypting the table with different combinations of
enclave-enabled and non-enclave keys and encryption types.
The sequence of operations is the following:
1. Create an encrypted table with two columns for each AE-supported data type,
one encrypted and one not encrypted.
2. Insert some data.
3. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
4. Ensure the two results are the same.
5. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,36 @@
<?php
// For AE v2, need ODBC driver 17.4 or above, an enclave enabled
// SQL Server, and a HGS server. The HGS server and SQL Server
// are the same for testing purposes.
if (!extension_loaded("sqlsrv")) {
die("skip Extension not loaded");
}
require_once("MsSetup.inc");
$connectionInfo = array("UID"=>$uid, "PWD"=>$pwd, "Driver" => $driver);
$conn = sqlsrv_connect( $server, $connectionInfo );
if ($conn === false) {
die( "skip Could not connect during SKIPIF." );
}
$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$msodbcsql_min = explode(".", $msodbcsql_ver)[1];
if ($msodbcsql_maj < 17) {
die("skip Unsupported ODBC driver version");
}
if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) {
die("skip Unsupported ODBC driver version");
}
// Get SQL Server
$server_info = sqlsrv_server_info($conn);
if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) {
die("skip Server is not HGS enabled");
}
?>

View file

@ -0,0 +1,991 @@
<?php
// runPlaintextTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place plaintext encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
// Create and populate a non-encrypted table
$createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
$stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName");
$stmt = sqlsrv_query($conn, $createQuery);
if(!$stmt) {
print_r(sqlsrv_errors());
die("Creating a plaintext table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
// Encrypt the table
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitdataTypes = array_chunk($dataTypes, 5);
foreach ($splitdataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength);
$isEncrypted = encryptTable($conn, $alterQuery, $key, $encryptionType, $attestationType);
}
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if ($key == $targetKey and $encryptionType == $targetType and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitdataTypes = array_chunk($dataTypes, 5);
foreach ($splitdataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// runEncryptedTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place re-encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
// Cannot create a table with encrypted data if CE is disabled
// TODO: Since we can create an empty encrypted table with
// CE disabled, account for the case where CE is disabled.
if ($ceValue == 'disabled') continue;
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
// Create and populate an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
$stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName");
$stmt = sqlsrv_query($conn, $createQuery);
if(!$stmt) {
print_r(sqlsrv_errors());
die("Creating an encrypted table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
$ceDisabled = $attestationType == 'disabled' ? true : false;
insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled);
$isEncrypted = true;
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
$initiallyEnclaveEncryption = isEnclaveEnabled($key);
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitdataTypes = array_chunk($dataTypes, 5);
foreach ($splitdataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType, true, $initiallyEnclaveEncryption);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// Connect and clear the procedure cache
function connect($attestationInfo)
{
require("MsSetup.inc");
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'CharacterSet'=>'UTF-8',
'ColumnEncryption'=>$attestationInfo,
'TraceOn'=>true,
'TraceOn'=>'c:\Users\davidp\Documents\SQL.LOG',
);
if ($keystore == 'akv') {
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$securityInfo = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVPrincipalName,
'KeyStoreSecret'=>$AKVPassword,
);
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$securityInfo = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVClientID,
'KeyStoreSecret'=>$AKVSecret,
);
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options = array_merge($options, $securityInfo);
}
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
$e = sqlsrv_errors();
checkErrors($e, array('CE400', '0'));
return false;
}
else
{
// Check that enclave computations are enabled
// See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave
$query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
print_r(sqlsrv_errors());
die("Error when checking if enclave computations are enabled. This should never happen! Non-HGS servers should have been skipped.\n");
} else {
$info = sqlsrv_fetch_array($stmt);
if (empty($info) or ($info['value'] != 1) or ($info['value_in_use'] != 1)) {
die("Error: enclave computations are not enabled on the server!");
}
}
// Enable rich computations
sqlsrv_query($conn, "DBCC traceon(127,-1);");
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
}
return $conn;
}
// This CREATE TABLE query simply creates a non-encrypted table with
// two columns for each data type side by side
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer,
// c_integer_AE integer
// )
function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength."), \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n ";
} else {
$query = $query.$colNames[$type]." ".$type.", \n ";
$query = $query.$colNamesAE[$type]." ".$type.", \n ";
}
}
// Remove the ", \n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -7)."\n)";
return $query;
}
// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must
// be preceded by ALTER TABLE
// This produces a query that looks like
// ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_integer_AE] integer
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_bigint_AE] bigint
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength)
{
$query = '';
foreach ($dataTypes as $dataType) {
$plength = dataTypeIsString($dataType) ? "(".$slength.")" : "";
$collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : "";
$query = $query." ALTER TABLE [dbo].[".$tableName."]
ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate."
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH
(ONLINE = ON);";
}
$query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;";
return $query;
}
// This CREATE TABLE query creates a table with two columns for
// each data type side by side, one plaintext and one encrypted
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer NULL,
// c_integer_AE integer
// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
// )
function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
$collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : "";
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
} else {
$query = $query.$colNames[$type]." ".$type." NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type." \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
}
}
// Remove the ",\n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -6)."\n)";
return $query;
}
// The INSERT query for the table
function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE)
{
$queryTypes = "(";
$valuesString = "VALUES (";
foreach ($dataTypes as $type) {
$colName1 = $colNames[$type].", ";
$colName2 = $colNamesAE[$type].", ";
$queryTypes .= $colName1;
$queryTypes .= $colName2;
$valuesString .= "?, ?, ";
}
// Remove the ", " from the end of the query or the comma will cause a syntax error
$queryTypes = substr($queryTypes, 0, -2).")";
$valuesString = substr($valuesString, 0, -2).")";
$insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString;
return $insertQuery;
}
function insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled=false)
{
global $length;
if (empty($testValues)) {
die("$testValues is empty or non-existent. Please check the required values file.\n");
}
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
// Use pack() on binary data
$params = array();
foreach ($dataTypes as $type) {
$SQLType = getSQLType($type, $length);
$PHPType = getPHPType($type);
$val = dataTypeIsBinary($type) ? pack('H*', $testValues[$type][$v]) : $testValues[$type][$v];
$params[] = array($val, SQLSRV_PARAM_IN, $PHPType, $SQLType);
$params[] = array($val, SQLSRV_PARAM_IN, $PHPType, $SQLType);
}
// Insert the data using sqlsrv_prepare()
$stmt = sqlsrv_prepare($conn, $insertQuery, $params);
if ($stmt == false) {
if (!$ceDisabled) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at prepare\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('22018', '206'));
}
}
if (sqlsrv_execute($stmt) == false) {
if (!$ceDisabled) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at execute\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('22018', '206'));
}
}
sqlsrv_free_stmt($stmt);
}
}
// encryptTable attempts to encrypt the table in place and verifies
// if it works given the attestation info and key type.
// Arguments:
// resource $conn: The connection
// string $alterQuery: The query to encrypt the table
// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl'
// bool $sameKeyAndType: Whether the key and encryption type are same for re-encrypting
// as for initial encryption.
// bool $initialEncryption: Whether we are testing with table initially encrypted, instead
// of plaintext being encrypted after creation
// bool $initiallyEnclaveEncrypted: Whether the table was initally encrypted with an
// enclave-enabled key
function encryptTable($conn, $alterQuery, $key, $encryptionType, $attestation, $sameKeyAndType=false, $initialEncryption=false, $initallyEnclaveEncrypted=false)
{
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if ($sameKeyAndType) {
print_r(sqlsrv_errors());
die("Encrypting table should not fail when target encryption key and type are the same as source: attestation $attestation, key $key and encryption type $encryptionType\n");
} elseif ($initialEncryption and !$initallyEnclaveEncrypted) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
} elseif ($attestation == 'correct') {
if (isEnclaveEnabled($key)) {
print_r(sqlsrv_errors());
die("Encrypting with correct attestation failed when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'enabled' or $attestation == 'disabled') {
if (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33546'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'wrongurl') {
if (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('CE405', '0'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'invalid') {
die("Encrypting table with invalid protocol! Should not get here!\n");
} else {
die("Error! This is no-man's-land\n");
}
return false;
} else {
if ((!isEnclaveEnabled($key) or $attestation != 'correct') and !$sameKeyAndType) {
die("Encrypting should have failed with attestation $attestation, key $key and encryption type $encryptionType\n");
}
unset($stmt);
return true;
}
}
// compareResults checks that the results between the encrypted and non-encrypted
// columns are identical if statement execution succeeds. If statement execution
// fails, this function checks for the correct error.
// Arguments:
// statement $AEstmt: Prepared statement fetching encrypted data
// statement $nonAEstmt: Prepared statement fetching non-encrypted data
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
// string $comparison: Comparison operator
// string $type: Data type the comparison is operating on
function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison='', $type='')
{
if (!sqlsrv_execute($nonAEstmt)) {
print_r(sqlsrv_errors());
die("Executing non-AE computation statement failed!\n");
}
if(!sqlsrv_execute($AEstmt)) {
if (!$isEncrypted) {
die("Computation statement execution should not have failed for an unencrypted table: attestation $attestation, key $key and encryption type $encryptionType\n");
}
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33546'));
} elseif (!isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
} else {
print_r(sqlsrv_errors());
die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType");
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('CE405', '0'));
} elseif (!isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
} else {
print_r(sqlsrv_errors());
die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType");
}
} elseif ($attestation == 'correct') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
} elseif ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} else {
print_r(sqlsrv_errors());
die("Comparison failed for correct attestation when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n");
}
} elseif ($attestation == 'disabled') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
} elseif ($comparison == '=' or $comparison == '<>' or $encryptionType == 'Randomized') {
$e = sqlsrv_errors();
checkErrors($e, array('22018', '206'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} else {
print_r(sqlsrv_errors());
die("Unexpected error occurred in compareResults: attestation $attestation, key $key and encryption type $encryptionType\n");
}
} else {
// char and nchar may not return the same results - at this point
// we've verified that statement execution works so just return
// TODO: Check if this bug is fixed and if so, remove this if block
if ($type == 'char' or $type == 'nchar' or $type == 'binary') {
return;
}
while($AEres = sqlsrv_fetch_array($AEstmt, SQLSRV_FETCH_NUMERIC)) {
$nonAEres = sqlsrv_fetch_array($nonAEstmt, SQLSRV_FETCH_NUMERIC);
if (!$nonAEres) {
print_r($AEres);
print_r(sqlsrv_errors());
print_r("Too many AE results for operation $comparison and data type $type!\n");
} else {
$i = 0;
foreach ($AEres as $AEr) {
if ($AEr != $nonAEres[$i]) {
print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i]." and non-AE result ".$nonAEres[$i]."\n");
print_r(sqlsrv_errors());
}
++$i;
}
}
}
if ($rr = sqlsrv_fetch_array($nonAEstmt)) {
print_r($rr);
print_r(sqlsrv_errors());
print_r("Too many non-AE results for operation $comparison and data type $type!\n");
}
}
}
// testCompare selects based on a comparison in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Table name
// array $comparisons: Comparison operations from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc
// string $key: Name of the encryption key
// integer $length: Length of the string types, from AE_v2_values.inc
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
// bool $isEncrypted: Whether the table is encrypted
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation, $isEncrypted)
{
foreach ($comparisons as $comparison) {
foreach ($dataTypes as $type) {
// Unicode operations with AE require the PHPTYPE to be specified to
// UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE
// clause is left out, we get different results between the
// encrypted and non-encrypted columns (probably because the
// collation was only changed in the encryption query).
$string = dataTypeIsStringMax($type);
$unicode = dataTypeIsUnicode($type);
$collate = $string ? " COLLATE Latin1_General_BIN2" : "";
$phptype = getPHPType($type);
$threshold = dataTypeIsBinary($type) ? pack('H*', $thresholds[$type]) : $thresholds[$type];
$param = array(array($threshold, SQLSRV_PARAM_IN, $phptype, getSQLType($type, $length)));
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate;
$AEstmt = sqlsrv_prepare($conn, $AEQuery, $param);
if (!$AEstmt) {
print_r(sqlsrv_errors());
die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n");
}
$nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param);
if (!$nonAEstmt) {
print_r(sqlsrv_errors());
die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
// testPatternMatch selects based on a pattern in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Table name
// array $patterns: Strings to pattern match, from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl'
// bool $isEncrypted: Whether the table is encrypted
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation, $isEncrypted)
{
foreach ($dataTypes as $type) {
// TODO: Pattern matching doesn't work in AE for non-string types.
// This is for security reasons, follow up on it.
if (!dataTypeIsStringMax($type)) {
continue;
}
foreach ($patterns[$type] as $pattern) {
$patternarray = array($pattern,
$pattern."%",
"%".$pattern,
"%".$pattern."%",
);
foreach ($patternarray as $spattern) {
// Unicode operations with AE require the PHPTYPE to be specified as
// UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE
// clause is left out, we get different results between the
// encrypted and non-encrypted columns (probably because the
// collation was only changed in the encryption query).
// We must pass the length of the pattern matching string
// to the SQLTYPE instead of the field size, as we usually would,
// because otherwise we would get an empty result set.
// We need iconv_strlen to return the number of characters
// for unicode strings, since strlen returns the number of bytes.
$unicode = dataTypeIsUnicode($type);
$slength = $unicode ? iconv_strlen($spattern) : strlen($spattern);
$collate = $unicode ? " COLLATE Latin1_General_BIN2" : "";
$phptype = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null;
$sqltype = $unicode ? SQLSRV_SQLTYPE_NCHAR($slength) : SQLSRV_SQLTYPE_CHAR($slength);
$param = array(array($spattern, SQLSRV_PARAM_IN, $phptype, $sqltype));
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate;
// TODO: Add binary type support below. May need to use unset()
// as in insertValues().
$AEstmt = sqlsrv_prepare($conn, $AEQuery, $param);
if (!$AEstmt) {
print_r(sqlsrv_errors());
die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n");
}
$nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param);
if (!$nonAEstmt) {
print_r(sqlsrv_errors());
die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $pattern, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
}
// Check that the expected errors ($codes) is found in the output of sqlsrv_errors() ($errors)
function checkErrors($errors, ...$codes)
{
$codeFound = false;
foreach ($codes as $code) {
if ($code[0]==$errors[0][0] and $code[1]==$errors[0][1]) {
$codeFound = true;
break;
}
}
if ($codeFound == false) {
echo "Error: ";
print_r($errors);
echo "\nExpected: ";
print_r($codes);
echo "\n";
die("Error code not found.\n");
}
}
function isEnclaveEnabled($key)
{
return (strpos($key, '-enclave') !== false);
}
function dataTypeIsString($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"]));
}
function dataTypeIsStringMax($dataType)
{
return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeNeedsCollate($dataType)
{
return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeIsUnicode($dataType)
{
return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"]));
}
function dataTypeIsBinary($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "varbinary(max)"]));
}
function getPHPType($type)
{
switch($type) {
case "bigint":
case "integer":
case "smallint":
case "tinyint":
case "bit":
return SQLSRV_PHPTYPE_INT;
break;
case "real":
case "float":
case "double":
return SQLSRV_PHPTYPE_FLOAT;
break;
case "numeric":
case "money":
case "smallmoney":
case "time":
case "date":
case "datetime":
case "datetime2":
case "datetimeoffset":
case "smalldatetime":
case "xml":
case "uniqueidentifier":
case "char":
case "varchar":
case "varchar(max)":
return SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR);
break;
case "nchar":
case "nvarchar":
case "nvarchar(max)":
return SQLSRV_PHPTYPE_STRING('UTF-8');
break;
case "binary":
case "varbinary":
case "varbinary(max)":
return SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY);
break;
default:
die("Case is missing for $type type in GetPHPType.\n");
}
}
function getSQLType($type, $length)
{
switch($type) {
case "bigint":
return SQLSRV_SQLTYPE_BIGINT;
case "integer":
return SQLSRV_SQLTYPE_INT;
case "smallint":
return SQLSRV_SQLTYPE_SMALLINT;
case "tinyint":
return SQLSRV_SQLTYPE_TINYINT;
case "bit":
return SQLSRV_SQLTYPE_BIT;
case "real":
return SQLSRV_SQLTYPE_REAL;
case "float":
case "double":
return SQLSRV_SQLTYPE_FLOAT;
case "numeric":
return SQLSRV_SQLTYPE_NUMERIC(18,0);
case "time":
return SQLSRV_SQLTYPE_TIME;
case "date":
return SQLSRV_SQLTYPE_DATE;
case "datetime":
return SQLSRV_SQLTYPE_DATETIME;
case "datetime2":
return SQLSRV_SQLTYPE_DATETIME2;
case "datetimeoffset":
return SQLSRV_SQLTYPE_DATETIMEOFFSET;
case "smalldatetime":
return SQLSRV_SQLTYPE_SMALLDATETIME;
case "money":
return SQLSRV_SQLTYPE_MONEY;
case "smallmoney":
return SQLSRV_SQLTYPE_SMALLMONEY;
case "xml":
return SQLSRV_SQLTYPE_XML;
case "uniqueidentifier":
return SQLSRV_SQLTYPE_UNIQUEIDENTIFIER;
case "char":
return SQLSRV_SQLTYPE_CHAR($length);
case "varchar":
return SQLSRV_SQLTYPE_VARCHAR($length);
case "varchar(max)":
return SQLSRV_SQLTYPE_VARCHAR('max');
case "nchar":
return SQLSRV_SQLTYPE_NCHAR($length);
case "nvarchar":
return SQLSRV_SQLTYPE_NVARCHAR($length);
case "nvarchar(max)":
return SQLSRV_SQLTYPE_NVARCHAR('max');
case "binary":
return SQLSRV_SQLTYPE_BINARY($length);
break;
case "varbinary":
return SQLSRV_SQLTYPE_VARBINARY($length);
break;
case "varbinary(max)":
return SQLSRV_SQLTYPE_VARBINARY('max');
break;
default:
die("Case is missing for $type type in getSQLType.\n");
}
}
?>

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating a
plaintext table each time, then trying to encrypt it with different combinations
of enclave-enabled and non-enclave keys and encryption types. It then reconnects
and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting
the table with different target combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create a table in plaintext with two columns for each AE-supported data type.
2. Insert some data in plaintext.
3. Encrypt one column for each data type.
4. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
5. Ensure the two results are the same.
6. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating a
plaintext table each time, then trying to encrypt it with different combinations
of enclave-enabled and non-enclave keys and encryption types. It then reconnects
and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting
the table with different target combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create a table in plaintext with two columns for each AE-supported data type.
2. Insert some data in plaintext.
3. Encrypt one column for each data type.
4. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
5. Ensure the two results are the same.
6. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $targetTypes,
and $targetKeys to try re-encrypting the table with different combinations of
enclave-enabled and non-enclave keys and encryption types.
The sequence of operations is the following:
1. Create an encrypted table with two columns for each AE-supported data type,
one encrypted and one not encrypted.
2. Insert some data.
3. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
4. Ensure the two results are the same.
5. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $targetTypes,
and $targetKeys to try re-encrypting the table with different combinations of
enclave-enabled and non-enclave keys and encryption types.
The sequence of operations is the following:
1. Create an encrypted table with two columns for each AE-supported data type,
one encrypted and one not encrypted.
2. Insert some data.
3. Perform rich computations on each AE-enabled column (comparisons and pattern matching)
and compare the result to the same query on the corresponding non-AE column for each data type.
4. Ensure the two results are the same.
5. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -22,24 +22,24 @@ function generateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2)
$stmt = $conn->query($sql);
// Insert data
$sql = "INSERT INTO $tableName1 VALUES ( ?, ? )";
$sql = "INSERT INTO $tableName1 VALUES (?, ?)";
for ($t = 100; $t < 116; $t++) {
$stmt = $conn->prepare($sql);
$ts = substr(sha1($t), 0, 5);
$params = array( $t,$ts );
$params = array($t, $ts);
$stmt->execute($params);
}
// Create table
$sql = "CREATE TABLE $tableName2 ( c1 INT, c2 VARCHAR(40) )";
$sql = "CREATE TABLE $tableName2 (c1 INT, c2 VARCHAR(40))";
$stmt = $conn->query($sql);
// Insert data
$sql = "INSERT INTO $tableName2 VALUES ( ?, ? )";
$sql = "INSERT INTO $tableName2 VALUES (?, ?)";
for ($t = 200; $t < 209; $t++) {
$stmt = $conn->prepare($sql);
$ts = substr(sha1($t), 0, 5);
$params = array( $t,$ts );
$params = array($t, $ts);
$stmt->execute($params);
}
@ -52,8 +52,12 @@ function generateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2)
// Break connection by getting the session ID and killing it.
// Note that breaking a connection and testing reconnection requires a
// TCP/IP protocol connection (as opposed to a Shared Memory protocol).
// Wait one second before and after breaking to ensure the break occurs
// in the correct order, otherwise there may be timing issues in Linux
// that can cause tests to fail intermittently and unpredictably.
function breakConnection($conn, $conn_break)
{
sleep(1);
$stmt1 = $conn->query("SELECT @@SPID");
$obj = $stmt1->fetch(PDO::FETCH_NUM);
$spid = $obj[0];
@ -69,11 +73,11 @@ function dropTables($server, $uid, $pwd, $tableName1, $tableName2)
$conn = new PDO("sqlsrv:server = $server ; Database = $dbName ;", $uid, $pwd);
$query="IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1";
$stmt=$conn->query($query);
$query = "IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1";
$stmt = $conn->query($query);
$query="IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2";
$stmt=$conn->query($query);
$query = "IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2";
$stmt = $conn->query($query);
}
dropTables($server, $uid, $pwd, $tableName1, $tableName2);

View file

@ -9,6 +9,9 @@ No PDO::PARAM_ tpe specified when binding parameters
require_once("MsCommon_mid-refactor.inc");
require_once("AEData.inc");
$dataTypes = array("bit", "tinyint", "smallint", "int", "bigint", "decimal(18,5)", "numeric(10,5)", "float", "real");
// Note the size of a float is platform dependent, with a precision of roughly 14 digits
// http://php.net/manual/en/language.types.float.php
try {
$conn = connect();
foreach ($dataTypes as $dataType) {
@ -26,7 +29,7 @@ try {
if ($r === false) {
isIncompatibleTypesError($stmt, $dataType, "default type");
} else {
echo "****Encrypted default type is compatible with encrypted $dataType****\n";
echo "-----Encrypted default type is compatible with encrypted $dataType-----\n";
fetchAll($conn, $tbname);
}
dropTable($conn, $tbname);
@ -37,49 +40,49 @@ try {
echo $e->getMessage();
}
?>
--EXPECT--
--EXPECTREGEX--
Testing bit:
****Encrypted default type is compatible with encrypted bit****
-----Encrypted default type is compatible with encrypted bit-----
c_det: 1
c_rand: 0
Testing tinyint:
****Encrypted default type is compatible with encrypted tinyint****
-----Encrypted default type is compatible with encrypted tinyint-----
c_det: 0
c_rand: 255
Testing smallint:
****Encrypted default type is compatible with encrypted smallint****
-----Encrypted default type is compatible with encrypted smallint-----
c_det: -32767
c_rand: 32767
Testing int:
****Encrypted default type is compatible with encrypted int****
-----Encrypted default type is compatible with encrypted int-----
c_det: -2147483647
c_rand: 2147483647
Testing bigint:
****Encrypted default type is compatible with encrypted bigint****
-----Encrypted default type is compatible with encrypted bigint-----
c_det: -922337203685479936
c_rand: 922337203685479936
Testing decimal(18,5):
****Encrypted default type is compatible with encrypted decimal(18,5)****
c_det: -9223372036854.80000
c_rand: 9223372036854.80000
Testing decimal\(18,5\):
-----Encrypted default type is compatible with encrypted decimal\(18,5\)-----
c_det: -9223372036854\.80000
c_rand: 9223372036854\.80000
Testing numeric(10,5):
****Encrypted default type is compatible with encrypted numeric(10,5)****
c_det: -21474.83647
c_rand: 21474.83647
Testing numeric\(10,5\):
-----Encrypted default type is compatible with encrypted numeric\(10,5\)-----
c_det: -21474\.83647
c_rand: 21474\.83647
Testing float:
****Encrypted default type is compatible with encrypted float****
c_det: -9223372036.8547993
c_rand: 9223372036.8547993
-----Encrypted default type is compatible with encrypted float-----
c_det: (-9223372036\.8547993|-9223372036\.8547992)
c_rand: (9223372036\.8547993|9223372036\.8547992)
Testing real:
****Encrypted default type is compatible with encrypted real****
c_det: -2147.4829
c_rand: 2147.4829
-----Encrypted default type is compatible with encrypted real-----
c_det: (-2147\.4829|-2147\.483)
c_rand: (2147\.4829|2147\.483)

View file

@ -0,0 +1,229 @@
--TEST--
Prepare with cursor buffered and fetch a variety of types converted to different types
--DESCRIPTION--
Test various conversion functionalites for buffered queries with PDO_SQLSRV.
--SKIPIF--
<?php require_once('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
$tableName = 'pdoFetchingClientBuffer';
$violation = 'Restricted data type attribute violation';
$outOfRange = 'Numeric value out of range';
$truncation = 'Fractional truncation';
$epsilon = 0.00001;
function fetchAsUTF8($conn, $tableName, $inputs)
{
$query = "SELECT * FROM $tableName";
try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
// Fetch all fields as UTF-8 strings
for ($i = 0; $i < count($inputs); $i++) {
$stmt->execute();
$f = $stmt->fetchColumn($i);
if ($f !== $inputs[$i]) {
var_dump($f);
}
}
} catch (PdoException $e) {
echo "Caught exception in fetchAsUTF8:\n";
echo $e->getMessage() . PHP_EOL;
}
}
function fetchArray($conn, $tableName, $inputs)
{
$query = "SELECT * FROM $tableName";
try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();
// By default, even numeric or datetime fields are fetched as strings
$result = $stmt->fetch(PDO::FETCH_NUM);
for ($i = 0; $i < count($inputs); $i++) {
if ($result[$i] !== $inputs[$i]) {
var_dump($f);
}
}
} catch (PdoException $e) {
echo "Caught exception in fetchArray:\n";
echo $e->getMessage() . PHP_EOL;
}
}
function fetchBinaryAsNumber($conn, $tableName, $inputs)
{
global $violation;
$query = "SELECT c1 FROM $tableName";
try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED, PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE=>true));
$stmt->execute();
$stmt->bindColumn('c1', $binaryValue, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);
echo "in fetchBinaryAsNumber: exception should have been thrown!\n";
} catch (PdoException $e) {
// The varbinary field - expect the violation error
if (strpos($e->getMessage(), $violation) === false) {
echo "in fetchBinaryAsNumber: expected '$violation' but caught this:\n";
echo $e->getMessage() . PHP_EOL;
}
}
}
function fetchBinaryAsBinary($conn, $tableName, $inputs)
{
try {
$query = "SELECT c1 FROM $tableName";
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();
$stmt->bindColumn('c1', $binaryValue, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
$row = $stmt->fetch(PDO::FETCH_BOUND);
if ($binaryValue !== $inputs[0]) {
echo "Fetched binary value unexpected: $binaryValue\n";
}
} catch (PdoException $e) {
echo "Caught exception in fetchBinaryAsBinary:\n";
echo $e->getMessage() . PHP_EOL;
}
}
function fetchFloatAsInt($conn, $tableName)
{
global $truncation;
try {
$query = "SELECT c3 FROM $tableName";
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();
$stmt->bindColumn('c3', $floatValue, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);
// This should return SQL_SUCCESS_WITH_INFO with the truncation error
$info = $stmt->errorInfo();
if ($info[0] != '01S07' || $info[2] !== $truncation) {
print_r($stmt->errorInfo());
}
} catch (PdoException $e) {
echo "Caught exception in fetchFloatAsInt:\n";
echo $e->getMessage() . PHP_EOL;
}
}
function fetchCharAsInt($conn, $tableName, $column)
{
global $outOfRange;
try {
$query = "SELECT $column FROM $tableName";
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();
$stmt->bindColumn($column, $value, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);
// TODO 11297: fix this part outside Windows later
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
echo "in fetchCharAsInt: exception should have been thrown!\n";
} else {
if ($value != 0) {
var_dump($value);
}
}
} catch (PdoException $e) {
// The (n)varchar field - expect the outOfRange error
if (strpos($e->getMessage(), $outOfRange) === false) {
echo "in fetchCharAsInt ($column): expected '$outOfRange' but caught this:\n";
echo $e->getMessage() . PHP_EOL;
}
}
}
function fetchAsNumerics($conn, $tableName, $inputs)
{
// The following calls expect different errors
fetchFloatAsInt($conn, $tableName);
fetchCharAsInt($conn, $tableName, 'c6');
fetchCharAsInt($conn, $tableName, 'c7');
// The following should work
try {
$query = "SELECT c2, c4 FROM $tableName";
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();
$stmt->bindColumn('c2', $intValue, PDO::PARAM_INT);
$stmt->bindColumn('c4', $decValue, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);
if ($intValue !== intval($inputs[1])) {
var_dump($intValue);
}
if ($decValue !== intval($inputs[3])) {
var_dump($decValue);
}
} catch (PdoException $e) {
echo "Caught exception in fetchAsNumerics:\n";
echo $e->getMessage() . PHP_EOL;
}
}
try {
$conn = connect();
$conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7');
$types = array('varbinary(10)', 'int', 'float(53)', 'decimal(16, 6)', 'datetime2', 'varchar(50)', 'nvarchar(50)');
$inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäოрŀề');
// Create table
$colMeta = array(new ColumnMeta($types[0], $columns[0]),
new ColumnMeta($types[1], $columns[1]),
new ColumnMeta($types[2], $columns[2]),
new ColumnMeta($types[3], $columns[3]),
new ColumnMeta($types[4], $columns[4]),
new ColumnMeta($types[5], $columns[5]),
new ColumnMeta($types[6], $columns[6]));
createTable($conn, $tableName, $colMeta);
// Prepare the input values and insert one row
$query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($query);
for ($i = 0; $i < count($columns); $i++) {
if ($i == 0) {
$stmt->bindParam($i+1, $inputs[$i], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
} else {
$stmt->bindParam($i+1, $inputs[$i]);
}
}
$stmt->execute();
unset($stmt);
// Starting fetching using client buffers
fetchAsUTF8($conn, $tableName, $inputs);
fetchArray($conn, $tableName, $inputs);
fetchBinaryAsNumber($conn, $tableName, $inputs);
fetchBinaryAsBinary($conn, $tableName, $inputs);
fetchAsNumerics($conn, $tableName, $inputs);
// dropTable($conn, $tableName);
echo "Done\n";
unset($conn);
} catch (PdoException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
Done

View file

@ -1,57 +1,63 @@
--TEST--
Test new connection keyword ColumnEncryption
--DESCRIPTION--
Some test cases return errors as expected. For testing purposes, an enclave enabled
SQL Server and the HGS server are the same instance. If the server is HGS enabled,
the error message of one test case is not the same.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
$msodbcsql_maj = "";
$msodbcsqlMaj = "";
$hgsEnabled = true;
try
{
$conn = new PDO( "sqlsrv:server = $server", $uid, $pwd );
$msodbcsql_ver = $conn->getAttribute( PDO::ATTR_CLIENT_VERSION )['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
}
catch( PDOException $e )
{
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() );
print_r($e->getMessage());
echo "\n";
}
test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj );
testColumnEncryption($server, $uid, $pwd, $msodbcsqlMaj);
echo "Done";
function verify_output( $PDOerror, $expected )
function verifyOutput($PDOerror, $expected, $caseNum)
{
if( strpos( $PDOerror->getMessage(), $expected ) === false )
{
print_r( $PDOerror->getMessage() );
if (strpos($PDOerror->getMessage(), $expected) === false) {
echo "Test case $caseNum failed:\n";
print_r($PDOerror->getMessage());
echo "\n";
}
}
function test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj )
function testColumnEncryption($server, $uid, $pwd, $msodbcsqlMaj)
{
global $hgsEnabled;
// Only works for ODBC 17
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = Enabled;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
if($msodbcsql_maj < 17)
{
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd);
} catch (PDOException $e) {
if ($msodbcsqlMaj < 17) {
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
verify_output( $e, $expected );
}
else
{
print_r( $e->getMessage() );
verifyOutput($e, $expected, "1");
} else {
echo "Test case 1 failed:\n";
print_r($e->getMessage());
echo "\n";
}
}
@ -59,50 +65,42 @@ function test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj )
// Works for ODBC 17, ODBC 13
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = Disabled;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
if($msodbcsql_maj < 13)
{
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd);
} catch (PDOException $e) {
if ($msodbcsqlMaj < 13) {
$expected = "Invalid connection string attribute";
verify_output( $e, $expected );
}
else
{
print_r( $e->getMessage() );
verifyOutput($e, $expected, "2");
} else {
echo "Test case 2 failed:\n";
print_r($e->getMessage());
echo "\n";
}
}
// should fail for all ODBC drivers
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
if ($hgsEnabled) {
$expected = "Requested attestation protocol is invalid.";
}
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = false;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
verify_output( $e, $expected );
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd);
} catch (PDOException $e) {
verifyOutput($e, $expected, "3");
}
// should fail for all ODBC drivers
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = 1;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
try {
$conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd);
} catch (PDOException $e) {
verifyOutput($e, $expected, "4");
}
catch( PDOException $e )
{
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
verify_output( $e, $expected );
}
}
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,65 @@
--TEST--
Test simple logging with connection, simple query and then close
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function toConnect()
{
require("MsSetup.inc");
// Basic connection
$dsn = getDSN($server, $databaseName, $driver);
$conn = new PDO($dsn, $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
}
try {
ini_set('log_errors', '1');
$logFilename = 'php_errors.log';
$logFilepath = dirname(__FILE__).'/'.$logFilename;
if (file_exists($logFilepath)) {
unlink($logFilepath);
}
ini_set('error_log', $logFilepath);
ini_set('pdo_sqlsrv.log_severity', '-1');
$conn = toConnect();
$stmt = $conn->query("SELECT @@Version");
// Ignore the fetch results
$stmt->fetchAll();
unset($conn);
if (file_exists($logFilepath)) {
echo file_get_contents($logFilepath);
unlink($logFilepath);
} else {
echo "$logFilepath is missing!\n";
}
echo "Done\n";
} catch (Exception $e) {
var_dump($e);
}
?>
--EXPECTF--
[%s UTC] pdo_sqlsrv_db_handle_factory: entering
[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000
[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701
[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to '%s'.
[%s UTC] pdo_sqlsrv_dbh_prepare: entering
[%s UTC] pdo_sqlsrv_stmt_execute: entering
[%s UTC] pdo_sqlsrv_stmt_describe_col: entering
[%s UTC] pdo_sqlsrv_stmt_fetch: entering
[%s UTC] pdo_sqlsrv_stmt_get_col_data: entering
[%s UTC] pdo_sqlsrv_stmt_fetch: entering
Done

View file

@ -0,0 +1,116 @@
--TEST--
Test different error modes. The queries will try to do a select on a non-existing table
--DESCRIPTION--
This is similar to pdo_errorMode.phpt but will display the contents of php
error logs based on log severity.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function toConnect()
{
require("MsSetup.inc");
$dsn = getDSN($server, $databaseName, $driver);
$conn = new PDO($dsn, $uid, $pwd);
return $conn;
}
function testException($conn)
{
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
global $sql;
try {
$q = $conn->query($sql);
} catch (Exception $e) {
// do nothing
}
}
function testWarning($conn)
{
// This forces PHP to log errors rather than displaying errors
// on screen -- only required for PDO::ERRMODE_WARNING
ini_set('display_errors', '0');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
global $sql;
$q = $conn->query($sql);
}
function runtests($severity)
{
global $conn;
$logFilename = 'php_errors' . $severity . '.log';
$logFilepath = dirname(__FILE__).'/'.$logFilename;
if (file_exists($logFilepath)) {
unlink($logFilepath);
}
ini_set('error_log', $logFilepath);
ini_set('pdo_sqlsrv.log_severity', $severity);
if ($severity === '2' ) {
testWarning($conn);
} else {
testException($conn);
}
if (file_exists($logFilepath)) {
if ($severity == '0') {
echo "$logFilepath should not exist\n";
}
echo file_get_contents($logFilepath);
unlink($logFilepath);
}
// Now reset logging by disabling it
ini_set('pdo_sqlsrv.log_severity', '0');
echo "Done with $severity\n\n";
}
try {
ini_set('log_errors', '1');
ini_set('pdo_sqlsrv.log_severity', '0');
$conn = toConnect();
$sql = "SELECT * FROM temp_table";
runtests('0');
runtests('1');
runtests('2');
runtests('4');
runtests('-1');
} catch (Exception $e) {
var_dump($e);
}
?>
--EXPECTF--
Done with 0
[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02
[%s UTC] pdo_sqlsrv_stmt_execute: error code = 208
[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'.
Done with 1
[%s UTC] PHP Warning: PDO::query(): SQLSTATE[42S02]: Base table or view not found: 208 %s[SQL Server]Invalid object name 'temp_table'. in %spdo_errorMode_logs.php on line %d
Done with 2
[%s UTC] pdo_sqlsrv_stmt_dtor: entering
[%s UTC] pdo_sqlsrv_dbh_prepare: entering
[%s UTC] pdo_sqlsrv_stmt_execute: entering
Done with 4
[%s UTC] pdo_sqlsrv_stmt_dtor: entering
[%s UTC] pdo_sqlsrv_dbh_prepare: entering
[%s UTC] pdo_sqlsrv_stmt_execute: entering
[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02
[%s UTC] pdo_sqlsrv_stmt_execute: error code = 208
[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'.
Done with -1

View file

@ -80,34 +80,34 @@ try {
}
?>
--EXPECT--
--EXPECTF--
Prepare without emulate prepare:
Array
(
[c1_decimal] => 422
[c2_money] => 132.2220
[c3_float] => 622.22000000000003
[c3_float] => 622.22%S
)
Prepare with emulate prepare and no bind param options:
Array
(
[c1_decimal] => 422
[c2_money] => 132.2220
[c3_float] => 622.22000000000003
[c3_float] => 622.22%S
)
Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM:
Array
(
[c1_decimal] => 422
[c2_money] => 132.2220
[c3_float] => 622.22000000000003
[c3_float] => 622.22%S
)
Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:
Array
(
[c1_decimal] => 422
[c2_money] => 132.2220
[c3_float] => 622.22000000000003
[c3_float] => 622.22%S
)
Prepare with emulate prepare and SQLSRV_ENCODING_BINARY:
No results for this query

View file

@ -79,34 +79,34 @@ try {
}
?>
--EXPECT--
--EXPECTF--
Prepare without emulate prepare:
Array
(
[c1_decimal] => 411
[c2_money] => 131.1100
[c3_float] => 611.11099999999999
[c3_float] => 611.1109999999999%d
)
Prepare with emulate prepare and no bind param options:
Array
(
[c1_decimal] => 411
[c2_money] => 131.1100
[c3_float] => 611.11099999999999
[c3_float] => 611.1109999999999%d
)
Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM:
Array
(
[c1_decimal] => 411
[c2_money] => 131.1100
[c3_float] => 611.11099999999999
[c3_float] => 611.1109999999999%d
)
Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:
Array
(
[c1_decimal] => 411
[c2_money] => 131.1100
[c3_float] => 611.11099999999999
[c3_float] => 611.1109999999999%d
)
Prepare with emulate prepare and SQLSRV_ENCODING_BINARY:
No results for this query

View file

@ -80,34 +80,34 @@ try {
}
?>
--EXPECT--
--EXPECTF--
Prepare without emulate prepare:
Array
(
[c1_decimal] => 433
[c2_money] => 133.3333
[c3_float] => 633.33333000000005
[c3_float] => 633.3333300000000%d
)
Prepare with emulate prepare and no bind param options:
Array
(
[c1_decimal] => 433
[c2_money] => 133.3333
[c3_float] => 633.33333000000005
[c3_float] => 633.3333300000000%d
)
Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM:
Array
(
[c1_decimal] => 433
[c2_money] => 133.3333
[c3_float] => 633.33333000000005
[c3_float] => 633.3333300000000%d
)
Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:
Array
(
[c1_decimal] => 433
[c2_money] => 133.3333
[c3_float] => 633.33333000000005
[c3_float] => 633.3333300000000%d
)
Prepare with emulate prepare and SQLSRV_ENCODING_BINARY:
No results for this query

View file

@ -47,6 +47,10 @@ try {
verifyResult($result);
// test not streamable types
// The size of a float is platform dependent, with a precision of roughly 14 digits
// http://php.net/manual/en/language.types.float.php
// For example, the input value for column [real_type] in setup\test_types.sql is 1.18E-38
// but in some distros the fetched value is 1.1799999E-38
$tsql = "SELECT * FROM [test_types]";
$stmt = $conn->query($tsql);
$result = $stmt->fetch(PDO::FETCH_NUM);
@ -60,19 +64,19 @@ unset($stmt);
unset($conn);
?>
--EXPECT--
--EXPECTREGEX--
Array
(
[0] => 9223372036854775807
[1] => 2147483647
[2] => 32767
[3] => 255
[4] => 1
[5] => 9999999999999999999999999999999999999
[6] => 922337203685477.5807
[7] => 214748.3647
[8] => 1.79E+308
[9] => 1.1799999E-38
[10] => 1968-12-12 16:20:00.000
[11] =>
)
\(
\[0\] => 9223372036854775807
\[1\] => 2147483647
\[2\] => 32767
\[3\] => 255
\[4\] => 1
\[5\] => 9999999999999999999999999999999999999
\[6\] => 922337203685477\.5807
\[7\] => 214748\.3647
\[8\] => 1\.79E\+308
\[9\] => (1\.18E-38|1\.1799999E-38)
\[10\] => 1968-12-12 16:20:00.000
\[11\] =>
\)

View file

@ -180,9 +180,9 @@ array(2) {
[5]=>
string(10) "STRINGCOL2"
["FloatCol"]=>
string(18) "222.22200000000001"
string(%d) "222.222%S"
[6]=>
string(18) "222.22200000000001"
string(%d) "222.222%S"
["XmlCol"]=>
string(431) "<xml> 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417.</xml>"
[7]=>
@ -395,7 +395,7 @@ object(stdClass)#%x (%x) {
["NVarCharCol"]=>
string(10) "STRINGCOL2"
["FloatCol"]=>
string(18) "222.22200000000001"
string(%d) "222.222%S"
["XmlCol"]=>
string(431) "<xml> 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417.</xml>"
}
@ -414,7 +414,7 @@ array(8) {
[5]=>
string(10) "STRINGCOL2"
[6]=>
string(18) "222.22200000000001"
string(%d) "222.222%S"
[7]=>
string(431) "<xml> 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417.</xml>"
}
@ -425,7 +425,7 @@ string(10) "STRINGCOL2"
string(23) "2000-11-11 11:11:11.223"
string(10) "STRINGCOL2"
string(10) "STRINGCOL2"
string(18) "222.22200000000001"
string(%d) "222.222%S"
string(431) "<xml> 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417.</xml>"
Test_9 : FETCH_INVALID :

View file

@ -31,7 +31,7 @@ try {
var_dump($e);
}
?>
--EXPECT--
--EXPECTF--
array(1) {
[0]=>
array(62) {
@ -224,9 +224,9 @@ array(2) {
[5]=>
string(10) "STRINGCOL2"
["FloatCol"]=>
string(18) "222.22200000000001"
string(%d) "222.222%S"
[6]=>
string(18) "222.22200000000001"
string(%d) "222.222%S"
["XmlCol"]=>
string(431) "<xml> 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417.</xml>"
[7]=>

View file

@ -3,34 +3,34 @@
// SQL Server, and a HGS server. The HGS server and SQL Server
// are the same for testing purposes.
if (!extension_loaded("sqlsrv")) {
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
require_once("MsSetup.inc");
require_once('MsSetup.inc');
$connectionInfo = array("UID"=>$uid, "PWD"=>$pwd, "Driver" => $driver);
$conn = sqlsrv_connect( $server, $connectionInfo );
if ($conn === false) {
die( "skip Could not connect during SKIPIF." );
$conn = new PDO("sqlsrv:server = $server", $uid, $pwd);
if (!$conn) {
die("skip Could not connect during SKIPIF.");
}
$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$msodbcsql_min = explode(".", $msodbcsql_ver)[1];
$msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer'];
$version = explode(".", $msodbcsqlVer);
if ($msodbcsql_maj < 17) {
$msodbcsqlMaj = $version[0];
$msodbcsqlMin = $version[1];
if ($msodbcsqlMaj < 17) {
die("skip Unsupported ODBC driver version");
}
if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) {
if ($msodbcsqlMin < 4 and $msodbcsqlMaj == 17) {
die("skip Unsupported ODBC driver version");
}
// Get SQL Server
$server_info = sqlsrv_server_info($conn);
if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) {
$serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO);
if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) {
die("skip Server is not HGS enabled");
}
?>
?>

View file

@ -42,6 +42,8 @@ if __name__ == '__main__':
parser.add_argument('-dbname', '--DBNAME', required=True)
parser.add_argument('-azure', '--AZURE', required=False, default='no')
args = parser.parse_args()
print("Start\n")
try:
server = os.environ['TEST_PHP_SQL_SERVER']
@ -61,10 +63,13 @@ if __name__ == '__main__':
if (args.AZURE.lower() == 'no'):
manageTestDB('create_db.sql', conn_options, args.DBNAME)
print("About to set up databases...\n")
# create tables in the new database
setupTestDatabase(conn_options, args.DBNAME, args.AZURE)
print("About to populate tables...\n")
# populate these tables
populateTables(conn_options, args.DBNAME)
print("About to set up encryption...\n")
# setup AE (certificate, column master key and column encryption key)
setupAE(conn_options, args.DBNAME)

View file

@ -12,7 +12,12 @@ Test for integer, float, and datetime types vs various sql server types.
require( 'MsCommon.inc' );
function get_fields( $stmt ) {
$epsilon = 0.00001;
$decimals = ['9999999999999999999999999999999999999', '-10000000000000000000000000000000000001', '0'];
function get_fields( $stmt, $round ) {
global $epsilon, $decimals;
// bigint
$field = sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_INT );
@ -71,7 +76,22 @@ function get_fields( $stmt ) {
}
else {
var_dump( sqlsrv_errors( SQLSRV_ERR_WARNINGS ) );
echo "$field\n";
// The size of a float is platform dependent, with a precision of roughly 14 digits
// http://php.net/manual/en/language.types.float.php
// For example, in Ubuntu 18.04 or macOS Mojave the returned value is 1.0E+37 or -1.0E+37
// but in Alpine Linux it is 9.9999999999997E+36 or -9.9999999999997E+36
if ($decimals[$round] == '0') {
if ($field != 0) {
echo "Expected 0 but got $field\n";
}
} else {
$expected = floatval($decimals[$round]);
$diff = abs(($field - $expected) / $expected);
if ($diff > $epsilon) {
echo "Expected $expected but got $field -- difference is $diff\n";
}
}
}
// datetime
@ -147,7 +167,7 @@ function get_fields( $stmt ) {
}
// maximum values
get_fields( $stmt );
get_fields( $stmt, 0 );
$success = sqlsrv_fetch( $stmt );
if( !$success ) {
@ -156,7 +176,7 @@ function get_fields( $stmt ) {
}
// minimum values
get_fields( $stmt );
get_fields( $stmt, 1 );
$success = sqlsrv_fetch( $stmt );
if( !$success ) {
@ -165,7 +185,7 @@ function get_fields( $stmt ) {
}
// zero values
get_fields( $stmt );
get_fields( $stmt, 2 );
$stmt = sqlsrv_query( $conn, "SELECT int_type, decimal_type, datetime_type, real_type FROM [test_types]" );
if( !$stmt ) {
@ -258,7 +278,6 @@ NULL
NULL
1
NULL
1.0E+37
NULL
12/12/1968 04:20:00
NULL
@ -296,7 +315,6 @@ NULL
NULL
0
NULL
-1.0E+37
NULL
12/12/1968 04:20:00
NULL
@ -319,7 +337,6 @@ NULL
NULL
0
NULL
0
NULL
12/12/1968 04:20:00
NULL

View file

@ -84,11 +84,9 @@ $sqlTypes = array(
function is_incompatible_types_error( $dataType, $sqlType )
{
$errors = sqlsrv_errors();
foreach ( $errors as $error )
{
foreach ($errors as $error) {
// 22018 is the SQLSTATE for the operand crash error for incompatible types
if ( $error['SQLSTATE'] == 22018 )
{
if ($error['SQLSTATE'] == '22018') {
echo "Encrypted $sqlType is incompatible with encrypted $dataType\n";
}
}
@ -109,7 +107,6 @@ function get_sqlType_constant( $sqlType )
{
switch ( $sqlType ) {
case 'SQLSRV_SQLTYPE_BIGINT':
case 'SQLSRV_SQLTYPE_BINARY':
case 'SQLSRV_SQLTYPE_BIT':
case 'SQLSRV_SQLTYPE_DATE':
case 'SQLSRV_SQLTYPE_DATETIME':
@ -135,6 +132,10 @@ function get_sqlType_constant( $sqlType )
case 'SQLSRV_SQLTYPE_XML':
return constant( $sqlType );
break;
case 'SQLSRV_SQLTYPE_BINARY':
// our tests always use precision 5 for SQLSRV_SQLTYPE_BINARY
return SQLSRV_SQLTYPE_BINARY(5);
break;
case 'SQLSRV_SQLTYPE_CHAR':
// our tests always use precision 5 for SQLSRV_SQLTYPE_CHAR
return SQLSRV_SQLTYPE_CHAR(5);
@ -146,7 +147,7 @@ function get_sqlType_constant( $sqlType )
case 'SQLSRV_SQLTYPE_NCHAR':
// our tests always use precision 5 for SQLSRV_SQLTYPE_NCHAR
return SQLSRV_SQLTYPE_NCHAR(5);
break;
break;
case 'SQLSRV_SQLTYPE_NUMERIC':
// our tests always use precision 10 scale 5 for SQLSRV_SQLTYPE_NUMERIC
return SQLSRV_SQLTYPE_NUMERIC(10, 5);
@ -157,7 +158,7 @@ function get_sqlType_constant( $sqlType )
}
}
function isDateTimeType( $sqlType )
function isDateTimeType($sqlType)
{
return ($sqlType == 'SQLSRV_SQLTYPE_DATE' ||
$sqlType == 'SQLSRV_SQLTYPE_DATETIME' ||
@ -167,4 +168,20 @@ function isDateTimeType( $sqlType )
$sqlType == 'SQLSRV_SQLTYPE_TIME');
}
function isLOBType($sqlType)
{
return ($sqlType == 'SQLSRV_SQLTYPE_TEXT' || $sqlType == 'SQLSRV_SQLTYPE_NTEXT' || $sqlType == 'SQLSRV_SQLTYPE_IMAGE');
}
function isCompatible($compatList, $dataType, $sqlType)
{
foreach ($compatList[$dataType] as $compatType) {
if (stripos($compatType, $sqlType) !== false) {
return true;
}
}
return false;
}
?>

View file

@ -84,6 +84,12 @@ function isDaasMode()
return ($daasMode ? true : false);
}
function isLocaleDisabled()
{
global $daasMode, $localeDisabled;
return ($daasMode || $localeDisabled);
}
function isSQLAzure()
{
// 'SQL Azure' indicates SQL Database or SQL Data Warehouse
@ -491,10 +497,11 @@ function handleErrors()
function setUSAnsiLocale()
{
// Do not run locale tests in Azure
if (isDaasMode()) {
// Do not run locale tests if locale disabled
if (isLocaleDisabled()) {
return;
}
if (!isWindows()) {
// macOS the locale names are different in Linux or macOS
$locale = strtoupper(PHP_OS) === 'LINUX' ? "en_US.ISO-8859-1" : "en_US.ISO8859-1";
@ -505,8 +512,8 @@ function setUSAnsiLocale()
function resetLocaleToDefault()
{
// Do not run locale tests in Azure
if (isDaasMode()) {
// Do not run locale tests if locale disabled
if (isLocaleDisabled()) {
return;
}
// Like setUSAnsiLocale() above, this method is only needed in non-Windows environment
@ -522,8 +529,8 @@ function isLocaleSupported()
if (isWindows()) {
return true;
}
// Do not run locale tests in Azure
if (isDaasMode()) {
// Do not run locale tests if locale disabled
if (isLocaleDisabled()) {
return false;
}
if (AE\isDataEncrypted()) {

View file

@ -25,6 +25,7 @@ $daasMode = false;
$marsMode = true;
$traceEnabled = false;
$localeDisabled = false;
$adServer = 'TARGET_AD_SERVER';
$adDatabase = 'TARGET_AD_DATABASE';

View file

@ -12,79 +12,79 @@ $tableName2 = "test_connres2";
// Using generated tables will eventually allow us to put the
// connection resiliency tests on Github, since the integrated testing
// from AppVeyor does not have AdventureWorks.
function GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 )
function GenerateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2)
{
$connectionInfo = array( "Database"=>$dbName, "uid"=>$uid, "pwd"=>$pwd );
$connectionInfo = array("Database"=>$dbName, "uid"=>$uid, "pwd"=>$pwd);
$conn = sqlsrv_connect( $server, $connectionInfo );
if ( $conn === false )
{
die ( print_r( sqlsrv_errors() ) );
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
die (print_r(sqlsrv_errors()));
}
// Create table
$sql = "CREATE TABLE $tableName1 ( c1 INT, c2 VARCHAR(40) )";
$stmt = sqlsrv_query( $conn, $sql );
$sql = "CREATE TABLE $tableName1 (c1 INT, c2 VARCHAR(40))";
$stmt = sqlsrv_query($conn, $sql);
// Insert data
$sql = "INSERT INTO $tableName1 VALUES ( ?, ? )";
for( $t = 100; $t < 116; $t++ )
{
$ts = substr( sha1( $t ),0,5 );
$params = array( $t,$ts );
$stmt = sqlsrv_prepare( $conn, $sql, $params );
sqlsrv_execute( $stmt );
$sql = "INSERT INTO $tableName1 VALUES (?, ?)";
for ($t = 100; $t < 116; $t++) {
$ts = substr(sha1($t), 0, 5);
$params = array($t, $ts);
$stmt = sqlsrv_prepare($conn, $sql, $params);
sqlsrv_execute($stmt);
}
// Create table
$sql = "CREATE TABLE $tableName2 ( c1 INT, c2 VARCHAR(40) )";
$stmt = sqlsrv_query( $conn, $sql );
$sql = "CREATE TABLE $tableName2 (c1 INT, c2 VARCHAR(40))";
$stmt = sqlsrv_query($conn, $sql);
// Insert data
$sql = "INSERT INTO $tableName2 VALUES ( ?, ? )";
for( $t = 200; $t < 209; $t++ )
{
$ts = substr( sha1( $t ),0,5 );
$params = array( $t,$ts );
$stmt = sqlsrv_prepare( $conn, $sql, $params );
sqlsrv_execute( $stmt );
$sql = "INSERT INTO $tableName2 VALUES (?, ?)";
for ($t = 200; $t < 209; $t++) {
$ts = substr(sha1($t), 0, 5);
$params = array($t, $ts);
$stmt = sqlsrv_prepare($conn, $sql, $params);
sqlsrv_execute($stmt);
}
sqlsrv_close( $conn );
sqlsrv_close($conn);
}
// Break connection by getting the session ID and killing it.
// Note that breaking a connection and testing reconnection requires a
// TCP/IP protocol connection (as opposed to a Shared Memory protocol).
function BreakConnection( $conn, $conn_break )
// Wait one second before and after breaking to ensure the break occurs
// in the correct order, otherwise there may be timing issues in Linux
// that can cause tests to fail intermittently and unpredictably.
function BreakConnection($conn, $conn_break)
{
$stmt1 = sqlsrv_query( $conn, "SELECT @@SPID" );
if ( sqlsrv_fetch( $stmt1 ) )
{
$spid=sqlsrv_get_field( $stmt1, 0 );
sleep(1);
$stmt1 = sqlsrv_query($conn, "SELECT @@SPID");
if (sqlsrv_fetch($stmt1)) {
$spid=sqlsrv_get_field($stmt1, 0);
}
$stmt2 = sqlsrv_prepare( $conn_break, "KILL ".$spid );
sqlsrv_execute( $stmt2 );
$stmt2 = sqlsrv_prepare($conn_break, "KILL ".$spid);
sqlsrv_execute($stmt2);
sleep(1);
}
// Remove the tables generated by GenerateTables
function DropTables( $server, $uid, $pwd, $tableName1, $tableName2 )
function DropTables($server, $uid, $pwd, $tableName1, $tableName2)
{
global $dbName;
$connectionInfo = array( "Database"=>$dbName, "UID"=>$uid, "PWD"=>$pwd );
$conn = sqlsrv_connect( $server, $connectionInfo );
$connectionInfo = array("Database"=>$dbName, "UID"=>$uid, "PWD"=>$pwd);
$conn = sqlsrv_connect($server, $connectionInfo);
$query="IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1";
$stmt=sqlsrv_query( $conn, $query );
$query = "IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1";
$stmt = sqlsrv_query($conn, $query);
$query="IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2";
$stmt=sqlsrv_query( $conn, $query );
$query = "IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2";
$stmt = sqlsrv_query($conn, $query);
}
DropTables( $server, $uid, $pwd, $tableName1, $tableName2 );
GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 );
DropTables($server, $uid, $pwd, $tableName1, $tableName2);
GenerateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2);
?>

View file

@ -9,11 +9,11 @@ if (!extension_loaded("sqlsrv")) {
require_once("MsSetup.inc");
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver);
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword);
$conn = sqlsrv_connect( $server, $connectionInfo );
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
die( "skip Could not connect during SKIPIF." );
die("skip Could not connect during SKIPIF.");
}
$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"];

View file

@ -52,7 +52,7 @@ foreach ($dataTypes as $dataType) {
}
}
// 22018 is the SQLSTATE for any incompatible conversion errors
if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) {
if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == '22018') {
echo "$sqlType should be compatible with $dataType\n";
$success = false;
}

View file

@ -1,111 +1,154 @@
--TEST--
Test for inserting and retrieving encrypted data of datetime types
--DESCRIPTION--
Bind output params using sqlsrv_prepare with all sql_type
Bind output/inout params using sqlsrv_prepare with all sql_type
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
date_default_timezone_set("Canada/Pacific");
$dataTypes = array("date", "datetime", "datetime2", "smalldatetime", "time", "datetimeoffset");
$directions = array("SQLSRV_PARAM_OUT", "SQLSRV_PARAM_INOUT");
// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine)
$compatList = array("date" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"datetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"datetime2" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"smalldatetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"time" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"datetimeoffset" => array("SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIMEOFFSET") );
$conn = AE\connect();
foreach ($dataTypes as $dataType) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName("", false);
$colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false));
AE\createTable($conn, $tbname, $colMetaArr);
if (AE\isColEncrypted()) {
// Create a Store Procedure
$spname = 'selectAllColumns';
createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname");
}
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
date_default_timezone_set("Canada/Pacific");
$dataTypes = array("date", "datetime", "datetime2", "smalldatetime", "time", "datetimeoffset");
$directions = array(SQLSRV_PARAM_OUT, SQLSRV_PARAM_INOUT);
// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine)
$compatList = array("date" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"datetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"datetime2" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"smalldatetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"time" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"),
"datetimeoffset" => array("SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIMEOFFSET") );
function testOutputParam($conn, $spname, $direction, $dataType, $sqlType)
{
// The driver does not support these types as output params, simply return
if (isDateTimeType($sqlType) || isLOBType($sqlType)) {
return true;
}
global $compatList;
$sqlTypeConstant = get_sqlType_constant($sqlType);
// Call store procedure
$outSql = AE\getCallProcSqlPlaceholders($spname, 2);
// Set these to NULL such that the PHP type of each output parameter is inferred
// from the SQLSRV_SQLTYPE_* constant
$c_detOut = null;
$c_randOut = null;
$stmt = sqlsrv_prepare(
$conn,
$outSql,
array(array( &$c_detOut, $direction, null, $sqlTypeConstant),
array(&$c_randOut, $direction, null, $sqlTypeConstant ))
);
if (!$stmt) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_execute($stmt);
$success = false;
$errors = sqlsrv_errors();
if (AE\IsDataEncrypted()) {
// With data encrypted, errors are totally expected
if (empty($errors)) {
echo "Encrypted data: $dataType should NOT be compatible with $sqlType\n";
} else {
// This should return 22018, the SQLSTATE for any incompatible conversion,
// except the XML type
$success = ($errors[0]['SQLSTATE'] === '22018');
if (!$success) {
if ($sqlType === 'SQLSRV_SQLTYPE_XML') {
$success = ($errors[0]['SQLSTATE'] === '42000');
} else {
echo "Encrypted data: unexpected errors with SQL type: $sqlType\n";
}
}
}
} else {
$compatible = isCompatible($compatList, $dataType, $sqlType);
if ($compatible) {
if (!empty($errors)) {
echo "$dataType should be compatible with $sqlType.\n";
} else {
$success = true;
}
} else {
$implicitConv = 'Implicit conversion from data type ';
// 22018 is the SQLSTATE for any incompatible conversion errors
if ($errors[0]['SQLSTATE'] === '22018') {
$success = true;
} elseif (strpos($errors[0]['message'], $implicitConv) !== false) {
$success = true;
} else {
echo "Failed with SQL type: $sqlType\n";
}
}
}
return $success;
}
////////////////////////////////////////////////////////////////////////////////////////
$conn = AE\connect();
foreach ($dataTypes as $dataType) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName("", false);
$colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false));
AE\createTable($conn, $tbname, $colMetaArr);
// Create a Store Procedure
$spname = 'selectAllColumns';
createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname");
// insert a row
// Take the second and third entres (some edge cases) from the various
// $[$dataType]_params in AEData.inc
// e.g. with $dataType = 'date', use $date_params[1] and $date_params[2]
// to form an array, namely ["0001-01-01", "9999-12-31"]
$inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2);
$r;
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r);
if ($r === false) {
is_incompatible_types_error($dataType, "default type");
}
foreach($directions as $direction) {
echo "Testing as $direction:\n";
// test each SQLSRV_SQLTYPE_ constants
foreach ($sqlTypes as $sqlType) {
if (!AE\isColEncrypted()) {
$isCompatible = false;
foreach ($compatList[$dataType] as $compatType) {
if (stripos($compatType, $sqlType) !== false) {
$isCompatible = true;
}
}
// 22018 is the SQLSTATE for any incompatible conversion errors
$errors = sqlsrv_errors();
if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) {
echo "$sqlType should be compatible with $dataType\n";
$success = false;
}
} else {
// skip unsupported datetime types
if (!isDateTimeType($sqlType)) {
$sqlTypeConstant = get_sqlType_constant($sqlType);
// Call store procedure
$outSql = AE\getCallProcSqlPlaceholders($spname, 2);
$c_detOut = '';
$c_randOut = '';
$stmt = sqlsrv_prepare( $conn, $outSql,
array(array( &$c_detOut, SQLSRV_PARAM_OUT, null, $sqlTypeConstant),
array(&$c_randOut, SQLSRV_PARAM_OUT, null, $sqlTypeConstant )));
if (!$stmt) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_execute($stmt);
$errors = sqlsrv_errors();
if (empty($errors) && AE\IsDataEncrypted()) {
// SQLSRV_PHPTYPE_DATETIME not supported
echo "$dataType should not be compatible with any datetime type.\n";
$success = false;
}
}
}
}
}
// cleanup
sqlsrv_free_stmt($stmt);
sqlsrv_query($conn, "TRUNCATE TABLE $tbname");
if ($success) {
echo "Test successfully done.\n";
}
if (AE\isColEncrypted()) {
dropProc($conn, $spname);
}
dropTable($conn, $tbname);
}
sqlsrv_close($conn);
$r;
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r);
if ($r === false) {
fatalError("Failed to insert data of type $dataType\n");
}
foreach ($directions as $direction) {
$dir = ($direction == SQLSRV_PARAM_OUT) ? 'SQLSRV_PARAM_OUT' : 'SQLSRV_PARAM_INOUT';
echo "Testing as $dir:\n";
// test each SQLSRV_SQLTYPE_* constants
foreach ($sqlTypes as $sqlType) {
$success = testOutputParam($conn, $spname, $direction, $dataType, $sqlType);
if (!$success) {
// No point to continue looping
echo("Test failed: $dataType as $sqlType\n");
die(print_r(sqlsrv_errors(), true));
}
}
}
// cleanup
sqlsrv_free_stmt($stmt);
sqlsrv_query($conn, "TRUNCATE TABLE $tbname");
dropProc($conn, $spname);
if ($success) {
echo "Test successfully done.\n";
}
dropTable($conn, $tbname);
}
sqlsrv_close($conn);
?>
--EXPECT--

View file

@ -5,143 +5,181 @@ Bind output params using sqlsrv_prepare with all sql_type
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
$dataTypes = array("bit", "tinyint", "smallint", "int", "bigint", "decimal(18,5)", "numeric(10,5)", "float", "real" );
$directions = array("SQLSRV_PARAM_OUT", "SQLSRV_PARAM_INOUT");
// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine)
$compatList = array("bit" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"tinyint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"smallint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"int" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"bigint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ),
"decimal(18,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"numeric(10,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"float" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT"),
"real" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT"));
$epsilon = 0.0001;
$conn = AE\connect();
foreach ($dataTypes as $dataType) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName("", false);
$colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false));
AE\createTable($conn, $tbname, $colMetaArr);
// TODO: It's a good idea to test conversions between different datatypes when AE is off as well.
if (AE\isColEncrypted()) {
// Create a Store Procedure
$spname = 'selectAllColumns';
createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname");
}
$directions = array(SQLSRV_PARAM_OUT, SQLSRV_PARAM_INOUT);
// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine)
$compatList = array("bit" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"tinyint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"smallint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"int" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"bigint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ),
"decimal(18,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"numeric(10,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"),
"float" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT"),
"real" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT"));
function compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues)
{
$epsilon = 0.0001;
$success = true;
if ($dataType == "float" || $dataType == "real") {
if (abs($c_detOut - $inputValues[0]) > $epsilon || abs($c_randOut - $inputValues[1]) > $epsilon) {
echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n";
print(" c_det: " . $c_detOut . "\n");
print(" c_rand: " . $c_randOut . "\n");
$success = false;
}
} else {
if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) {
echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n";
print(" c_det: " . $c_detOut . "\n");
print(" c_rand: " . $c_randOut . "\n");
$success = false;
}
}
return $success;
}
function testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues)
{
// The driver does not support these types as output params, simply return
if (isDateTimeType($sqlType) || isLOBType($sqlType)) {
return true;
}
global $compatList;
$sqlTypeConstant = get_sqlType_constant($sqlType);
// Call store procedure
$outSql = AE\getCallProcSqlPlaceholders($spname, 2);
// Set these to NULL such that the PHP type of each output parameter is inferred
// from the SQLSRV_SQLTYPE_* constant
$c_detOut = null;
$c_randOut = null;
$stmt = sqlsrv_prepare(
$conn,
$outSql,
array(array( &$c_detOut, $direction, null, $sqlTypeConstant),
array(&$c_randOut, $direction, null, $sqlTypeConstant ))
);
if (!$stmt) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_execute($stmt);
$success = false;
$errors = sqlsrv_errors();
if (AE\IsDataEncrypted()) {
if (empty($errors)) {
// With data encrypted, it's a lot stricter, so the results are expected
// to be numeric and comparable
$success = compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues);
} else {
// This should return 22018, the SQLSTATE for any incompatible conversion,
// except the XML type
$success = ($errors[0]['SQLSTATE'] === '22018');
if (!$success) {
if ($sqlType === 'SQLSRV_SQLTYPE_XML') {
$success = ($errors[0]['SQLSTATE'] === '42000');
} else {
echo "Encrypted data: unexpected errors with SQL type: $sqlType\n";
}
}
}
} else {
$compatible = isCompatible($compatList, $dataType, $sqlType);
if ($compatible && empty($errors)) {
$success = true;
} else {
// Even if $dataType is compatible with $sqlType sometimes
// we still get errors from the server -- if so, it might
// return either SQLSTATE '42000' or '22018' (operand type
// clash but only happens with some certain types)
// E.g. when converting a bigint to int or an int to numeric,
// SQLSTATE '42000' is returned, indicating an error when
// converting from one type to another.
// TODO 11559: investigate if SQLSTATE '42000' is indeed acceptable
$success = ($errors[0]['SQLSTATE'] === '42000' || ($errors[0]['SQLSTATE'] === '22018' && in_array($sqlType, ['SQLSRV_SQLTYPE_XML', 'SQLSRV_SQLTYPE_BINARY', 'SQLSRV_SQLTYPE_VARBINARY', 'SQLSRV_SQLTYPE_UNIQUEIDENTIFIER', 'SQLSRV_SQLTYPE_TIMESTAMP'])));
if (!$success) {
if ($compatible) {
echo "$dataType should be compatible with $sqlType.\n";
} else {
echo "Failed with SQL type: $sqlType\n";
}
}
}
}
return $success;
}
////////////////////////////////////////////////////////////////////////////////////////
$conn = AE\connect();
foreach ($dataTypes as $dataType) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName("", false);
$colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false));
AE\createTable($conn, $tbname, $colMetaArr);
// Create a Store Procedure
$spname = 'selectAllColumns';
createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname");
// insert a row
// Take the second and third entres (some edge cases) from the various
// $[$dataType]_params in AEData.inc
// e.g. with $dataType = 'decimal(18,5)', use $decimal_params[1] and $decimal_params[2]
// to form an array, namely [-9223372036854.80000, 9223372036854.80000]
$inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2);
$r;
// convert input values to strings for decimals and numerics
if ($dataTypes == "decimal(18,5)" || $dataTypes == "numeric(10,5)") {
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => (string) $inputValues[0], $colMetaArr[1]->colName => (string) $inputValues[1] ), $r);
} else {
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r);
}
if ($r === false) {
is_incompatible_types_error($dataType, "default type");
}
foreach($directions as $direction) {
echo "Testing as $direction:\n";
// test each SQLSRV_SQLTYPE_ constants
foreach ($sqlTypes as $sqlType) {
if (!AE\isColEncrypted()) {
$isCompatible = false;
foreach ($compatList[$dataType] as $compatType) {
if (stripos($compatType, $sqlType) !== false) {
$isCompatible = true;
}
}
// 22018 is the SQLSTATE for any incompatible conversion errors
$errors = sqlsrv_errors();
if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) {
echo "$sqlType should be compatible with $dataType\n";
$success = false;
}
} else {
// skip unsupported datetime types
if (!isDateTimeType($sqlType)) {
$sqlTypeConstant = get_sqlType_constant($sqlType);
// Call store procedure
$outSql = AE\getCallProcSqlPlaceholders($spname, 2);
if ($sqlType == 'SQLSRV_SQLTYPE_FLOAT' || $sqlType == 'SQLSRV_SQLTYPE_REAL') {
$c_detOut = 0.0;
$c_randOut = 0.0;
} else {
$c_detOut = 0;
$c_randOut = 0;
}
$stmt = sqlsrv_prepare($conn, $outSql,
array(array( &$c_detOut, constant($direction), null, $sqlTypeConstant),
array(&$c_randOut, constant($direction), null, $sqlTypeConstant)));
if (!$stmt) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_execute($stmt);
$errors = sqlsrv_errors();
if (!empty($errors)) {
if (stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) {
var_dump(sqlsrv_errors());
$success = false;
}
}
else {
if (AE\IsDataEncrypted() || stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) {
if ($dataType == "float" || $dataType == "real") {
if (abs($c_detOut - $inputValues[0]) > $epsilon || abs($c_randOut - $inputValues[1]) > $epsilon) {
echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n";
print(" c_det: " . $c_detOut . "\n");
print(" c_rand: " . $c_randOut . "\n");
$success = false;
}
} else {
if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) {
echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n";
print(" c_det: " . $c_detOut . "\n");
print(" c_rand: " . $c_randOut . "\n");
$success = false;
}
}
}
}
sqlsrv_free_stmt($stmt);
}
}
}
}
if (AE\isColEncrypted()) {
dropProc($conn, $spname);
}
if ($success) {
echo "Test successfully done.\n";
}
dropTable($conn, $tbname);
}
sqlsrv_close($conn);
$r;
// convert input values to strings for decimals and numerics
if ($dataTypes == "decimal(18,5)" || $dataTypes == "numeric(10,5)") {
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => (string) $inputValues[0], $colMetaArr[1]->colName => (string) $inputValues[1] ), $r);
} else {
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r);
}
if ($r === false) {
fatalError("Failed to insert data of type $dataType\n");
}
foreach ($directions as $direction) {
$dir = ($direction == SQLSRV_PARAM_OUT) ? 'SQLSRV_PARAM_OUT' : 'SQLSRV_PARAM_INOUT';
echo "Testing as $dir:\n";
// test each SQLSRV_SQLTYPE_ constants
foreach ($sqlTypes as $sqlType) {
$success = testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues);
if (!$success) {
// No point to continue looping
echo("Test failed: $dataType as $sqlType\n");
die(print_r(sqlsrv_errors(), true));
}
}
}
dropProc($conn, $spname);
if ($success) {
echo "Test successfully done.\n";
}
dropTable($conn, $tbname);
}
sqlsrv_close($conn);
?>
--EXPECT--

View file

@ -5,117 +5,155 @@ Bind output params using sqlsrv_prepare with all sql_type
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
$dataTypes = array("char(5)", "varchar(max)", "nchar(5)", "nvarchar(max)");
$directions = array("SQLSRV_PARAM_OUT", "SQLSRV_PARAM_INOUT");
// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine)
$compatList = array("char(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"),
"varchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"),
"nchar(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"),
"nvarchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"));
$conn = AE\connect();
foreach ($dataTypes as $dataType) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName("", false);
$colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false));
AE\createTable($conn, $tbname, $colMetaArr);
// TODO: It's a good idea to test conversions between different datatypes when AE is off as well.
if (AE\isColEncrypted()) {
// Create a Store Procedure
$spname = 'selectAllColumns';
createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname");
}
// insert a row
$inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2);
$r;
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r);
if ($r === false) {
is_incompatible_types_error($dataType, "default type");
}
foreach($directions as $direction) {
echo "Testing as $direction:\n";
// test each SQLSRV_SQLTYPE_ constants
foreach ($sqlTypes as $sqlType) {
if (!AE\isColEncrypted()) {
$isCompatible = false;
foreach ($compatList[$dataType] as $compatType) {
if (stripos($compatType, $sqlType) !== false) {
$isCompatible = true;
}
}
// 22018 is the SQLSTATE for any incompatible conversion errors
$errors = sqlsrv_errors();
if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) {
echo "$sqlType should be compatible with $dataType\n";
$success = false;
}
} else {
// skip unsupported datetime types
if (!isDateTimeType($sqlType)) {
$sqlTypeConstant = get_sqlType_constant($sqlType);
// Call store procedure
$outSql = AE\getCallProcSqlPlaceholders($spname, 2);
$c_detOut = '';
$c_randOut = '';
$stmt = sqlsrv_prepare($conn, $outSql,
array(array(&$c_detOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant),
array(&$c_randOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant)));
if (!$stmt) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_execute($stmt);
$errors = sqlsrv_errors();
if (!empty($errors) ) {
if (stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) {
var_dump(sqlsrv_errors());
$success = false;
}
}
else
{
if (AE\IsDataEncrypted() || stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) {
if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) {
echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n";
print(" c_det: " . $c_detOut . "\n");
print(" c_rand: " . $c_randOut . "\n");
$success = false;
}
}
}
sqlsrv_free_stmt($stmt);
}
}
}
}
$directions = array(SQLSRV_PARAM_OUT, SQLSRV_PARAM_INOUT);
// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine)
$compatList = array("char(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"),
"varchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"),
"nchar(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"),
"nvarchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"));
$conn = AE\connect();
function compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues)
{
$success = true;
if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) {
echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n";
print(" c_det: " . $c_detOut . "\n");
print(" c_rand: " . $c_randOut . "\n");
$success = false;
}
return $success;
}
function testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues)
{
// The driver does not support these types as output params, simply return
if (isDateTimeType($sqlType) || isLOBType($sqlType)) {
return true;
}
global $compatList;
if (AE\isColEncrypted()) {
dropProc($conn, $spname);
}
if ($success) {
echo "Test successfully done.\n";
}
dropTable($conn, $tbname);
}
sqlsrv_close($conn);
$sqlTypeConstant = get_sqlType_constant($sqlType);
// Call store procedure
$outSql = AE\getCallProcSqlPlaceholders($spname, 2);
// Set these to NULL such that the PHP type of each output parameter is inferred
// from the SQLSRV_SQLTYPE_* constant
$c_detOut = null;
$c_randOut = null;
$stmt = sqlsrv_prepare(
$conn,
$outSql,
array(array(&$c_detOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant),
array(&$c_randOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant))
);
if (!$stmt) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_execute($stmt);
$success = false;
$errors = sqlsrv_errors();
if (AE\IsDataEncrypted()) {
if (empty($errors)) {
// With data encrypted, it's a lot stricter, so the results are expected
// to be comparable
$success = compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues);
} else {
// This should return 22018, the SQLSTATE for any incompatible conversion,
// except the XML type
$success = ($errors[0]['SQLSTATE'] === '22018');
if (!$success) {
if ($sqlType === 'SQLSRV_SQLTYPE_XML') {
$success = ($errors[0]['SQLSTATE'] === '42000');
} else {
echo "Encrypted data: unexpected errors with SQL type: $sqlType\n";
}
}
}
} else {
$compatible = isCompatible($compatList, $dataType, $sqlType);
if ($compatible && empty($errors)) {
$success = true;
} else {
// Even if $dataType is compatible with $sqlType sometimes
// we still get errors from the server -- if so, it should
// return SQLSTATE '42000', indicating an error when
// converting from one type to another
// With data NOT encrypted, converting string types to other
// types will not return '22018'
$success = ($errors[0]['SQLSTATE'] === '42000');
if (!$success) {
echo "Failed with SQL type: $sqlType\n";
}
}
}
return $success;
}
////////////////////////////////////////////////////////////////////////////////////////
foreach ($dataTypes as $dataType) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName("", false);
$colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false));
AE\createTable($conn, $tbname, $colMetaArr);
// Create a Store Procedure
$spname = 'selectAllColumns';
createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname");
// insert a row
// Take the second and third entres from the various $[$dataType]_params in AEData.inc
// e.g. with $dataType = 'varchar(max)', use $varchar_params[1] and $varchar_params[2]
// to form an array
$inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2);
$r;
$stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r);
if ($r === false) {
fatalError("Failed to insert data of type $dataType\n");
}
foreach ($directions as $direction) {
$dir = ($direction == SQLSRV_PARAM_OUT) ? 'SQLSRV_PARAM_OUT' : 'SQLSRV_PARAM_INOUT';
echo "Testing as $dir:\n";
// test each SQLSRV_SQLTYPE_ constants
foreach ($sqlTypes as $sqlType) {
$success = testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues);
if (!$success) {
// No point to continue looping
echo("Test failed: $dataType as $sqlType\n");
die(print_r(sqlsrv_errors(), true));
}
}
}
dropProc($conn, $spname);
if ($success) {
echo "Test successfully done.\n";
}
dropTable($conn, $tbname);
}
sqlsrv_close($conn);
?>
--EXPECT--

View file

@ -0,0 +1,278 @@
--TEST--
Prepare with cursor buffered and fetch a variety of types converted to different types
--DESCRIPTION--
Test various conversion functionalites for buffered queries with SQLSRV.
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
$violation = 'Restricted data type attribute violation';
$outOfRange = 'Numeric value out of range';
$truncation = 'Fractional truncation';
$epsilon = 0.00001;
function fetchAsUTF8($conn, $tableName, $inputs)
{
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED));
if (!$stmt) {
fatalError("In fetchAsUTF8: failed to run query!");
}
if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) {
fatalError("In fetchAsUTF8: failed to fetch the row from $tableName!");
}
// Fetch all fields as UTF-8 strings
for ($i = 0; $i < count($inputs); $i++) {
$f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING('utf-8'));
if ($i == 0) {
if ($inputs[$i] !== hex2bin($f)) {
var_dump($f);
}
} else {
if ($f !== $inputs[$i]) {
var_dump($f);
}
}
}
}
function fetchArray($conn, $tableName, $inputs)
{
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_prepare($conn, $query, array(), array('Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true));
if (!$stmt) {
fatalError("In fetchArray: failed to prepare query!");
}
$res = sqlsrv_execute($stmt);
if (!$res) {
fatalError("In fetchArray: failed to execute query!");
}
// Fetch fields as an array
$results = sqlsrv_fetch_array($stmt);
if ($results === false) {
fatalError("In fetchArray: failed to fetch the row from $tableName!");
}
for ($i = 0; $i < count($inputs); $i++) {
if ($i == 1) {
$expected = intval($inputs[$i]);
} elseif ($i == 2) {
$expected = floatval($inputs[$i]);
} else {
$expected = $inputs[$i];
}
if ($results[$i] !== $expected) {
echo "in fetchArray: for column $i expected $expected but got: ";
var_dump($results[$i]);
}
}
}
function fetchAsFloats($conn, $tableName, $inputs)
{
global $violation, $outOfRange, $epsilon;
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true));
if (!$stmt) {
fatalError("In fetchAsFloats: failed to run query!");
}
if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) {
fatalError("In fetchAsFloats: failed to fetch the row from $tableName!");
}
// Fetch all fields as floats
for ($i = 0; $i < count($inputs); $i++) {
$f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_FLOAT);
if ($i == 0) {
// The varbinary field - expect the violation error
if (strpos(sqlsrv_errors()[0]['message'], $violation) === false) {
var_dump($f);
fatalError("in fetchAsFloats: expected $violation for column $i\n");
}
} elseif ($i < 5) {
$expected = floatval($inputs[$i]);
$diff = abs(($f - $expected) / $expected);
if ($diff > $epsilon) {
echo "in fetchAsFloats: for column $i expected $expected but got: ";
var_dump($f);
}
} else {
// The char fields will get errors too
// TODO 11297: fix this part outside Windows later
if (isWindows()) {
if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) {
var_dump($f);
fatalError("in fetchAsFloats: expected $outOfRange for column $i\n");
}
} else {
if ($f != 0.0) {
var_dump($f);
}
}
}
}
}
function fetchAsInts($conn, $tableName, $inputs)
{
global $violation, $outOfRange, $truncation;
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true));
if (!$stmt) {
fatalError("In fetchAsInts: failed to run query!");
}
if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) {
fatalError("In fetchAsInts: failed to fetch the row from $tableName!");
}
// Fetch all fields as integers
for ($i = 0; $i < count($inputs); $i++) {
$f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_INT);
if ($i == 0) {
// The varbinary field - expect the violation error
if (strpos(sqlsrv_errors()[0]['message'], $violation) === false) {
var_dump($f);
fatalError("in fetchAsInts: expected $violation for column $i\n");
}
} elseif ($i == 2) {
// The float field - expect truncation
if (strpos(sqlsrv_errors()[0]['message'], $truncation) === false) {
var_dump($f);
fatalError("in fetchAsInts: expected $truncation for column $i\n");
}
} elseif ($i >= 5) {
// The char fields will get errors too
// TODO 11297: fix this part outside Windows later
if (isWindows()) {
if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) {
var_dump($f);
fatalError("in fetchAsInts: expected $outOfRange for column $i\n");
}
} else {
if ($f != 0) {
var_dump($f);
}
}
} else {
$expected = floor($inputs[$i]);
if ($f != $expected) {
echo "in fetchAsInts: for column $i expected $expected but got: ";
var_dump($f);
}
}
}
}
function fetchAsBinary($conn, $tableName, $inputs)
{
$query = "SELECT c_varbinary FROM $tableName";
$stmt = sqlsrv_prepare($conn, $query, array(), array('Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED));
if (!$stmt) {
fatalError("In fetchAsBinary: failed to prepare query!");
}
$res = sqlsrv_execute($stmt);
if (!$res) {
fatalError("In fetchAsBinary: failed to execute query!");
}
if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) {
fatalError("In fetchAsInts: failed to fetch the row from $tableName!");
}
// Fetch the varbinary field as is
$f = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM("binary"));
if (gettype($f) !== 'resource') {
var_dump($f);
}
// Do not expect errors
$errs = sqlsrv_errors();
if (!empty($errs)) {
var_dump($errs);
}
// Check its value
while (!feof($f)) {
$str = fread($f, 80);
}
if (trim($str) !== $inputs[0]) {
echo "Fetched binary value unexpected: $str\n";
}
}
require_once('MsCommon.inc');
$conn = AE\connect(array('CharacterSet' => 'UTF-8'));
$tableName = 'srvFetchingClientBuffer';
// Create table
$names = array('c_varbinary', 'c_int', 'c_float', 'c_decimal', 'c_datetime2', 'c_varchar', 'c_nvarchar');
$columns = array(new AE\ColumnMeta('varbinary(10)', $names[0]),
new AE\ColumnMeta('int', $names[1]),
new AE\ColumnMeta('float(53)', $names[2]),
new AE\ColumnMeta('decimal(16, 6)', $names[3]),
new AE\ColumnMeta('datetime2', $names[4]),
new AE\ColumnMeta('varchar(50)', $names[5]),
new AE\ColumnMeta('nvarchar(50)', $names[6]));
$stmt = AE\createTable($conn, $tableName, $columns);
if (!$stmt) {
fatalError("Failed to create $tableName!");
}
// Prepare the input values
$inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäოрŀề');
$params = array(array(bin2hex($inputs[0]), SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_BINARY(10)),
$inputs[1], $inputs[2], $inputs[3], $inputs[4], $inputs[5],
array($inputs[6], SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('utf-8')));
// Form the insert query
$colStr = '(';
foreach ($names as $name) {
$colStr .= $name . ", ";
}
$colStr = rtrim($colStr, ", ") . ") ";
$insertSql = "INSERT INTO [$tableName] " . $colStr . 'VALUES (?,?,?,?,?,?,?)';
// Insert one row only
$stmt = sqlsrv_prepare($conn, $insertSql, $params);
if ($stmt) {
$res = sqlsrv_execute($stmt);
if (!$res) {
fatalError("Failed to execute insert statement to $tableName!");
}
} else {
fatalError("Failed to prepare insert statement to $tableName!");
}
// Starting fetching using client buffers
fetchAsUTF8($conn, $tableName, $inputs);
fetchArray($conn, $tableName, $inputs);
fetchAsFloats($conn, $tableName, $inputs);
fetchAsInts($conn, $tableName, $inputs);
fetchAsBinary($conn, $tableName, $inputs);
dropTable($conn, $tableName);
echo "Done\n";
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECT--
Done

View file

@ -0,0 +1,63 @@
--TEST--
Test sqlsrv_commit method with logging
--DESCRIPTION--
Similar to sqlsrv_commit.phpt but also test some basic logging activities
By adding integer values together, we can specify more than one logging option at a time.
SQLSRV_LOG_SYSTEM_CONN (2) Turns on logging of connection activity.
SQLSRV_LOG_SYSTEM_STMT (4) Turns on logging of statement activity.
For example, sqlsrv.LogSubsystems = 6
turns on logging of connection and statement activities
--SKIPIF--
<?php require_once('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_NOTICE);
sqlsrv_configure('LogSubsystems', 6);
require_once('MsCommon.inc');
$conn = connect();
if (!$conn) {
fatalError("Could not connect");
}
$stmt1 = sqlsrv_query($conn, "IF OBJECT_ID('Products', 'U') IS NOT NULL DROP TABLE Products");
$stmt1 = sqlsrv_query($conn, "CREATE TABLE Products (ProductID int PRIMARY KEY, ProductName nvarchar(40), CategoryID int, UnitPrice money)");
if ($stmt1 === false) {
die(print_r(sqlsrv_errors(), true));
}
sqlsrv_free_stmt($stmt1);
$stmt2 = sqlsrv_query($conn, "INSERT INTO Products (ProductID, ProductName, CategoryID, UnitPrice) VALUES (1, 'TestProduct2', 2, '13.55')");
$stmt3 = sqlsrv_query($conn, "SELECT * FROM Products WHERE CategoryID = 2");
if ($stmt2 && $stmt3) {
sqlsrv_commit($conn);
}
$stmt1 = sqlsrv_query($conn, "DROP TABLE Products");
sqlsrv_free_stmt($stmt1);
sqlsrv_free_stmt($stmt2);
sqlsrv_free_stmt($stmt3);
sqlsrv_close($conn);
?>
--EXPECT--
sqlsrv_connect: entering
sqlsrv_query: entering
sqlsrv_query: entering
sqlsrv_stmt_dtor: entering
sqlsrv_free_stmt: entering
sqlsrv_stmt_dtor: entering
sqlsrv_query: entering
sqlsrv_query: entering
sqlsrv_commit: entering
sqlsrv_query: entering
sqlsrv_free_stmt: entering
sqlsrv_stmt_dtor: entering
sqlsrv_free_stmt: entering
sqlsrv_stmt_dtor: entering
sqlsrv_free_stmt: entering
sqlsrv_stmt_dtor: entering
sqlsrv_close: entering

View file

@ -14,7 +14,7 @@ functions return FALSE for errors.
fatalError("sqlsrv_connect should have returned false.");
}
$conn = sqlsrv_connect("_!@#$", array( "Driver" => "Danica Patrick" ));
$conn = sqlsrv_connect("_!@#$", array( "Driver" => "Wrong Driver" ));
if ($conn !== false) {
fatalError("sqlsrv_connect should have returned false.");
}

View file

@ -1,98 +1,101 @@
--TEST--
Test new connection keyword ColumnEncryption
Test new connection keyword ColumnEncryption with different input values
--DESCRIPTION--
Some test cases return errors as expected. For testing purposes, an enclave enabled
SQL Server and the HGS server are the same instance. If the server is HGS enabled,
the error message of one test case is not the same.
--SKIPIF--
<?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);
test_ColumnEncryption($server, $connectionOptions);
testColumnEncryption($server, $connectionOptions);
echo "Done";
function test_ColumnEncryption($server ,$connectionOptions){
function testColumnEncryption($server, $connectionOptions)
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
{
if ($conn === false) {
print_r(sqlsrv_errors());
}
$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$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;
}
// Only works for ODBC 17
$connectionOptions['ColumnEncryption']='Enabled';
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
if($msodbcsql_maj < 17){
$connectionOptions['ColumnEncryption'] = 'Enabled';
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {
if ($msodbcsqlMaj < 17) {
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
if( strcasecmp(sqlsrv_errors($conn)[0]['message'], $expected ) != 0 )
{
if (strcasecmp(sqlsrv_errors($conn)[0]['message'], $expected) != 0) {
print_r(sqlsrv_errors());
}
}
else
{
} else {
echo "Test case 1 failed:\n";
print_r(sqlsrv_errors());
}
}
// Works for ODBC 17, ODBC 13
$connectionOptions['ColumnEncryption']='Disabled';
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
if($msodbcsql_maj < 13)
{
$expected_substr = "Invalid connection string attribute";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {
if ($msodbcsqlMaj < 13) {
$expected = "Invalid connection string attribute";
if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) {
print_r(sqlsrv_errors());
}
}
else
{
} else {
echo "Test case 2 failed:\n";
print_r(sqlsrv_errors());
}
}
else
{
} else {
sqlsrv_close($conn);
}
// Should fail for all ODBC drivers - but the error message returned depends on the server
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
if ($hgsEnabled) {
$expected = "Requested attestation protocol is invalid.";
}
// should fail for all ODBC drivers
$connectionOptions['ColumnEncryption']='false';
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
$expected_substr = "Invalid value specified for connection string attribute 'ColumnEncryption'";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {
if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) {
echo "Test case 3 failed:\n";
print_r(sqlsrv_errors());
}
}
// should fail for all ODBC drivers
$expected = "Invalid value type for option ColumnEncryption was specified. String type was expected.";
// should fail for all ODBC drivers with the above error message
$connectionOptions['ColumnEncryption']=true;
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
$expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected.";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {
if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) {
echo "Test case 4 failed:\n";
print_r(sqlsrv_errors());
}
}
// should fail for all ODBC drivers
// should fail for all ODBC drivers with the above error message
$connectionOptions['ColumnEncryption']=false;
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
$expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected.";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {
if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) {
echo "Test case 5 failed:\n";
print_r(sqlsrv_errors());
}
}

View file

@ -0,0 +1,56 @@
--TEST--
Test functions return FALSE for errors with logging
--DESCRIPTION--
Similar to sqlsrv_connect_logs.phpt but this time test logging to a log file
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once('MsSetup.inc');
$logFilename = 'php_errors.log';
$logFilepath = dirname(__FILE__).'/'.$logFilename;
if (file_exists($logFilepath)) {
unlink($logFilepath);
}
ini_set('log_errors', '1');
ini_set('error_log', $logFilepath);
ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_ALL);
ini_set("sqlsrv.LogSubsystems", SQLSRV_LOG_SYSTEM_ALL);
$conn = sqlsrv_connect($server, array( "Driver" => "Wrong Driver" ));
if ($conn !== false) {
fatalError("sqlsrv_connect should have returned false.");
}
ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_NOTICE);
$conn = sqlsrv_connect($server, array( "uid" => $uid , "pwd" => $pwd ));
if ($conn === false) {
fatalError("sqlsrv_connect should have connected.");
}
ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_ERROR);
$stmt = sqlsrv_query($conn, "SELECT * FROM some_bogus_table");
if ($stmt !== false) {
fatalError("sqlsrv_query should have returned false.");
}
ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_ALL);
if (file_exists($logFilepath)) {
echo file_get_contents($logFilepath);
unlink($logFilepath);
}
sqlsrv_close($conn);
?>
--EXPECTF--
[%s UTC] sqlsrv_connect: entering
[%s UTC] sqlsrv_connect: SQLSTATE = IMSSP
[%s UTC] sqlsrv_connect: error code = -106
[%s UTC] sqlsrv_connect: message = Invalid value Wrong Driver was specified for Driver option.
[%s UTC] sqlsrv_connect: entering
[%s UTC] sqlsrv_query: SQLSTATE = 42S02
[%s UTC] sqlsrv_query: error code = 208
[%s UTC] sqlsrv_query: message = %s[SQL Server]Invalid object name 'some_bogus_table'.

View file

@ -0,0 +1,49 @@
--TEST--
Test functions return FALSE for errors with logging
--DESCRIPTION--
Similar to sqlsrv_connect.phpt but also test different settings of logging activities
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure('WarningsReturnAsErrors', 0);
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
sqlsrv_configure('LogSubsystems', SQLSRV_LOG_SYSTEM_ALL);
require_once('MsSetup.inc');
$conn = sqlsrv_connect($server, array( "Driver" => "Wrong Driver" ));
if ($conn !== false) {
fatalError("sqlsrv_connect should have returned false.");
}
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_NOTICE);
$conn = sqlsrv_connect($server, array( "uid" => $uid , "pwd" => $pwd ));
if ($conn === false) {
fatalError("sqlsrv_connect should have connected.");
}
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ERROR);
$stmt = sqlsrv_query($conn, "SELECT * FROM some_bogus_table");
if ($stmt !== false) {
fatalError("sqlsrv_query should have returned false.");
}
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_WARNING);
sqlsrv_close($conn);
?>
--EXPECTF--
sqlsrv.LogSubsystems = -1
sqlsrv_connect: entering
sqlsrv_connect: SQLSTATE = IMSSP
sqlsrv_connect: error code = -106
sqlsrv_connect: message = Invalid value Wrong Driver was specified for Driver option.
sqlsrv_configure: entering
sqlsrv.LogSeverity = 4
sqlsrv_connect: entering
sqlsrv_configure: entering
sqlsrv_query: SQLSTATE = 42S02
sqlsrv_query: error code = 208
sqlsrv_query: message = %s[SQL Server]Invalid object name 'some_bogus_table'.

View file

@ -268,7 +268,7 @@ NULL
NULL
1
NULL
1\.0E\+37
(1\.0E\+37|9.9999999999997E\+36)
NULL
12\/12\/1968 04\:20\:00
NULL
@ -306,7 +306,7 @@ NULL
NULL
0
NULL
\-1\.0E\+37
(\-1\.0E\+37|-9.9999999999997E\+36)
NULL
12\/12\/1968 04\:20\:00
NULL

View file

@ -7,21 +7,24 @@ Intentionally provide an invalid server name and set LoginTimeout. Verify the ti
--FILE--
<?php
require_once('MsSetup.inc');
$serverName = "WRONG_SERVER_NAME";
// Based on the following reference, a login timeout of less than approximately 10 seconds
// is not reliable. The defaut is 15 seconds so we fix it at 20 seconds.
// https://docs.microsoft.com/sql/connect/odbc/windows/features-of-the-microsoft-odbc-driver-for-sql-server-on-windows
$timeout = 20;
$maxAttempts = 3;
$numAttempts = 0;
$leeway = 1.0;
// The difference in time elapsed is platform dependent, and in some distros, such as Alpine or Suse, extra delay may be caused by the attempts to resolve non-existent hostnames
// Set leeway to 2 seconds to allow some room of such errors
$leeway = 2.0;
$missed = false;
do {
$t0 = microtime(true);
$conn = sqlsrv_connect($serverName , array("LoginTimeout" => $timeout));
$numAttempts++;
@ -38,7 +41,7 @@ do {
echo "Connection failed at $elapsed secs. Leeway is $leeway sec but the difference is $diff\n";
} else {
// The test will fail but this helps us decide if this test should be redesigned
echo "$numAttempts\t";
echo "Attempts: $numAttempts, Time difference: $diff\n";
sleep(5);
}
}

View file

@ -67,6 +67,10 @@ var_dump($array);
$array = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
var_dump($array);
// The size of a float is platform dependent, with a precision of roughly 14 digits
// http://php.net/manual/en/language.types.float.php
$epsilon = 0.00001;
$numFields = sqlsrv_num_fields($stmt);
$meta = sqlsrv_field_metadata($stmt);
$rowcount = sqlsrv_num_rows($stmt);
@ -81,8 +85,21 @@ for ($i = 0; $i < $rowcount; $i++) {
$field = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_INT);
var_dump($field);
}
$field = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_FLOAT);
var_dump($field);
$field1 = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_FLOAT);
if ($j > 5) {
// these are the zero fields
$expected = 0.0;
if ($field1 !== $expected) {
echo "Expected $expected but got $field1\n";
}
} else {
$expected = floatval($field);
$diff = abs(($field1 - $expected) / $expected);
if ($diff > $epsilon) {
echo "Expected $expected but got $field1 -- difference is $diff\n";
}
}
}
}
@ -136,78 +153,60 @@ array(9) {
column: a
string(15) "1234567890.1234"
float(1234567890.1234)
column: neg_a
string(16) "-1234567890.1234"
float(-1234567890.1234)
column: b
string(1) "1"
int(1)
float(1)
column: neg_b
string(2) "-1"
int(-1)
float(-1)
column: c
string(7) ".500000"
float(0.5)
column: neg_c
string(8) "-.550000"
float(-0.55)
column: zero
string(1) "0"
int(0)
float(0)
column: zerof
string(1) "0"
float(0)
column: zerod
string(7) ".000000"
float(0)
column: a
string(3) "0.5"
float(0.5)
column: neg_a
string(5) "-0.55"
float(-0.55)
column: b
string(6) "100000"
int(100000)
float(100000)
column: neg_b
string(8) "-1234567"
int(-1234567)
float(-1234567)
column: c
string(17) "1234567890.123400"
float(1234567890.1234)
column: neg_c
string(18) "-1234567890.123400"
float(-1234567890.1234)
column: zero
string(1) "0"
int(0)
float(0)
column: zerof
string(1) "0"
float(0)
column: zerod
string(7) ".000000"
float(0)
string(7) ".000000"