5.7.1-preview (#1064)

This commit is contained in:
Jenny Tam 2019-12-03 13:28:29 -08:00 committed by GitHub
parent bd06cf3e3f
commit 8aaace8e46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 3757 additions and 199 deletions

View file

@ -20,10 +20,10 @@ env:
- TEST_PHP_SQL_PWD=Password123
before_install:
- docker pull mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu
- docker pull mcr.microsoft.com/mssql/server:2019-GA-ubuntu-16.04
install:
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu
- docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql .
before_script:

View file

@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## 5.7.1-preview - 2019-12-03
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 7.4
- Support for Red Hat 8 and macOS Catalina (10.15)
- Feature Request [#1018](https://github.com/microsoft/msphpsql/issues/1018) - support for [PHP extended string types](https://github.com/microsoft/msphpsql/wiki/Features#natlTypes) - Pull Request [#1043](https://github.com/microsoft/msphpsql/pull/1043)
- [Always Encrypted with secure enclaves](https://github.com/microsoft/msphpsql/wiki/Features#alwaysencryptedV2), which requires [MS ODBC Driver 17.4+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019)
### Removed
- Dropped support for [PHP 7.1](https://www.php.net/supported-versions.php)
### Fixed
- Issue [#1027](https://github.com/microsoft/msphpsql/issues/1027) - Fixed how drivers handle query timeout settings
- Pull Request [#1049](https://github.com/microsoft/msphpsql/pull/1049) - performance improvement for fetching from tables with many columns - cached the derived php types with column metadata to streamline data retrieval
### 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
- Data Classification metadata retrieval requires ODBC Driver 17.4.2.1+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019)
- 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.7.0-preview - 2019-09-05
Updated PECL release packages. Here is the list of updates:

View file

@ -1,50 +1,50 @@
# 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, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12 and 15, and macOS 10.11, 10.12, 10.13, and 10.14. 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, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu, RedHat, Debian, Suse, and macOS. These instructions advise installing the drivers using PECL, but you may 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.3 by default. Note that some supported Linux distros default to PHP 7.0 or earlier, which is not supported for the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.1 or 7.2 instead.
These instructions install PHP 7.4 by default. Note that some supported Linux distros default to PHP 7.1 or earlier, which the PHP drivers for SQL Server no longer support. When installing PHP 7.2 or above, please read the notes at the beginning of each section below.
## Contents of this page:
- [Installing the drivers on Ubuntu 16.04, 18.04, and 19.04](#installing-the-drivers-on-ubuntu-1604-1804-and-1904)
- [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7)
- [Installing the drivers on Red Hat 7 and 8](#installing-the-drivers-on-red-hat-7-and-8)
- [Installing the drivers on Debian 8, 9 and 10](#installing-the-drivers-on-debian-8-9-and-10)
- [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15)
- [Installing the drivers on macOS Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-sierra-high-sierra-and-mojave)
- [Installing the drivers on macOS Sierra, High Sierra, Mojave, and Catalina](#installing-the-drivers-on-macos-sierra-high-sierra-mojave-and-catalina)
## Installing the drivers on Ubuntu 16.04, 18.04, and 19.04
> [!NOTE]
> To install PHP 7.1 or 7.2, replace 7.3 with 7.1 or 7.2 in the following commands.
> To install PHP 7.3 or 7.2, replace 7.4 with 7.3 or 7.2 in the following commands.
### Step 1. Install PHP
```
sudo su
add-apt-repository ppa:ondrej/php -y
apt-get update
apt-get install php7.3 php7.3-dev php7.3-xml -y --allow-unauthenticated
apt-get install php7.4 php7.4-dev php7.4-xml -y --allow-unauthenticated
```
### Step 2. Install prerequisites
Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo pecl install sqlsrv-5.7.1preview
sudo pecl install pdo_sqlsrv-5.7.1preview
sudo su
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini
exit
sudo phpenmod -v 7.3 sqlsrv pdo_sqlsrv
sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv
```
If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`.
### Step 4. Install Apache and configure driver loading
```
sudo su
apt-get install libapache2-mod-php7.3 apache2
apt-get install libapache2-mod-php7.4 apache2
a2dismod mpm_event
a2enmod mpm_prefork
a2enmod php7.3
a2enmod php7.4
exit
```
### Step 5. Restart Apache and test the sample script
@ -53,10 +53,10 @@ sudo service apache2 restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Red Hat 7
## Installing the drivers on Red Hat 7 and 8
> [!NOTE]
> To install PHP 7.1 or 7.2, replace remi-php73 with remi-php71 or remi-php72 respectively in the following commands.
> To install PHP 7.3 or 7.2, replace remi-php74 with remi-php73 or remi-php72 respectively in the following commands.
### Step 1. Install PHP
@ -67,14 +67,14 @@ wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm
subscription-manager repos --enable=rhel-7-server-optional-rpms
yum install yum-utils
yum-config-manager --enable remi-php73
yum-config-manager --enable remi-php74
yum update
yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc
```
### Step 2. Install prerequisites
Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
Install the ODBC driver for Red Hat 7 and 8 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
Compiling the PHP drivers with PECL with PHP 7.2 or 7.3 requires a more recent GCC than the default:
In some versions of Red Hat 7, compiling the PHP drivers with PECL and PHP 7.2 requires a more recent GCC than the default:
```
sudo yum-config-manager --enable rhel-server-rhscl-7-rpms
sudo yum install devtoolset-7
@ -82,8 +82,8 @@ scl enable devtoolset-7 bash
```
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo pecl install sqlsrv-5.7.1preview
sudo pecl install pdo_sqlsrv-5.7.1preview
sudo su
echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/30-pdo_sqlsrv.ini
echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini
@ -91,9 +91,9 @@ exit
```
An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv):
```
pecl download sqlsrv
tar xvzf sqlsrv-5.7.0.tgz
cd sqlsrv-5.7.0/
pecl download sqlsrv-5.7.1preview
tar xvzf sqlsrv-5.7.1preview.tgz
cd sqlsrv-5.7.1preview/
phpize
./configure --with-php-config=/usr/bin/php-config
make
@ -120,7 +120,7 @@ To test your installation, see [Testing your installation](#testing-your-install
## Installing the drivers on Debian 8, 9 and 10
> [!NOTE]
> To install PHP 7.1 or 7.2, replace 7.3 in the following commands with 7.1 or 7.2.
> To install PHP 7.3 or 7.2, replace 7.4 in the following commands with 7.3 or 7.2.
### Step 1. Install PHP
```
@ -129,7 +129,7 @@ apt-get install curl apt-transport-https
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
apt-get update
apt-get install -y php7.3 php7.3-dev php7.3-xml
apt-get install -y php7.4 php7.4-dev php7.4-xml
```
### Step 2. Install prerequisites
Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
@ -143,23 +143,23 @@ locale-gen
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo pecl install sqlsrv-5.7.1preview
sudo pecl install pdo_sqlsrv-5.7.1preview
sudo su
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini
exit
sudo phpenmod -v 7.3 sqlsrv pdo_sqlsrv
sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv
```
If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`.
### Step 4. Install Apache and configure driver loading
```
sudo su
apt-get install libapache2-mod-php7.3 apache2
apt-get install libapache2-mod-php7.4 apache2
a2dismod mpm_event
a2enmod mpm_prefork
a2enmod php7.3
a2enmod php7.4
```
### Step 5. Restart Apache and test the sample script
```
@ -173,9 +173,9 @@ To test your installation, see [Testing your installation](#testing-your-install
> In the following instructions, replace <SuseVersion> with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1. For Suse 12, use SLE_12_SP4 (or above if applicable). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse.
> [!NOTE]
> Packages for PHP 7.3 are not available for Suse 12.
> To install PHP 7.1, replace the repository URL below with the following URL:
`https://download.opensuse.org/repositories/devel:/languages:/php:/php71/<SuseVersion>/devel:languages:php:php71.repo`.
> Packages for PHP 7.4 are not available for Suse 12.
> To install PHP 7.3, replace the repository URL below with the following URL:
`https://download.opensuse.org/repositories/devel:/languages:/php:/php73/<SuseVersion>/devel:languages:php:php73.repo`.
> To install PHP 7.2, replace the repository URL below with the following URL:
`https://download.opensuse.org/repositories/devel:/languages:/php:/php72/<SuseVersion>/devel:languages:php:php72.repo`.
@ -194,8 +194,8 @@ Install the ODBC driver for Suse by following the instructions on the [Linux and
> If you get an error message saying `Connection to 'pecl.php.net:443' failed: Unable to find the socket transport "ssl"`, edit the pecl script at /usr/bin/pecl and remove the `-n` switch in the last line. This switch prevents PECL from loading ini files when PHP is called, which prevents the OpenSSL extension from loading.
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo pecl install sqlsrv-5.7.1preview
sudo pecl install pdo_sqlsrv-5.7.1preview
sudo su
echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/pdo_sqlsrv.ini
echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/sqlsrv.ini
@ -216,7 +216,7 @@ sudo systemctl restart apache2
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on macOS Sierra, High Sierra, and Mojave
## Installing the drivers on macOS Sierra, High Sierra, Mojave, and Catalina
If you do not already have it, install brew as follows:
```
@ -224,18 +224,18 @@ If you do not already have it, install brew as follows:
```
> [!NOTE]
> To install PHP 7.1 or 7.2, replace php@7.3 with php@7.1 or php@7.2 respectively in the following commands.
> To install PHP 7.3 or 7.2, replace php@7.4 with php@7.3 or php@7.2 respectively in the following commands.
### Step 1. Install PHP
```
brew tap
brew tap homebrew/core
brew install php@7.3
brew install php@7.4
```
PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following:
```
brew link --force --overwrite php@7.3
brew link --force --overwrite php@7.4
```
### Step 2. Install prerequisites
@ -248,8 +248,8 @@ brew install autoconf automake libtool
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo pecl install sqlsrv-5.7.1preview
sudo pecl install pdo_sqlsrv-5.7.1preview
```
### Step 4. Install Apache and configure driver loading
```
@ -261,7 +261,7 @@ apachectl -V | grep SERVER_CONFIG_FILE
```
and substitute the path for `httpd.conf` in the following commands:
```
echo "LoadModule php7_module /usr/local/opt/php@7.3/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf
echo "LoadModule php7_module /usr/local/opt/php@7.4/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf
(echo "<FilesMatch .php$>"; echo "SetHandler application/x-httpd-php"; echo "</FilesMatch>";) >> /usr/local/etc/httpd/httpd.conf
```
### Step 5. Restart Apache and test the sample script

View file

@ -2,13 +2,13 @@
**Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server**
The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) to handle the low-level communication with SQL Server.
The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server.
This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some [limitations](https://github.com/Microsoft/msphpsql/releases). Upcoming releases will contain additional functionalities, bug fixes, and more.
This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some limitations. Upcoming [releases][releases] will contain additional functionalities, bug fixes, and more.
## Take our survey
Thank you for taking the time to participate in our last survey. You can continue to help us improve by letting us know how we are doing and how you use PHP by taking our December pulse survey:
Thank you for taking the time to participate in the [sentiment survey](https://github.com/microsoft/msphpsql/wiki/Survey-Results). You can continue to help us improve by letting us know how we are doing and how you use [PHP][phpweb]:
<a href="https://aka.ms/mssqlphpsurvey"><img style="float: right;" height="67" width="156" src="https://sqlchoice.blob.core.windows.net/sqlchoice/static/images/survey.png"></a>
@ -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/dev/graph/badge.svg
[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/master/graph/badge.svg
[codecov-site]: https://codecov.io/gh/microsoft/msphpsql
## Get Started
@ -40,22 +40,22 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co
## Announcements
Please visit the [blog][blog] for more announcements.
Please follow [SQL Server Drivers][sqldrivers] for announcements.
## Prerequisites
For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs.
On the client machine:
- PHP 7.1.x, 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows), or 7.3.x
- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server)
- PHP 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows), 7.3.x, or 7.4.x
- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11][odbcdoc]
- If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP
On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux.
## Building and Installing the Drivers on Windows
The drivers are distributed as pre-compiled extensions for PHP found on the [releases page](https://github.com/Microsoft/msphpsql/releases). They are available in thread-safe and non thread-safe versions, and in 32-bit and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need.
The drivers are distributed as pre-compiled extensions for PHP found on the [releases page][releases]. They are available in thread-safe and non thread-safe versions, and in 32-bit and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need.
If you choose to build the drivers, you must be able to build PHP 7.* without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually.
@ -65,13 +65,13 @@ Finally, if running PHP in a Web server, restart the Web server.
## Install (UNIX)
For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac).
For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs][unixinstructions].
## Sample Code
For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver).
## Limitations and Known Issues
Please refer to [Releases](https://github.com/Microsoft/msphpsql/releases) for the latest limitations and known issues.
Please refer to [Releases][releases] for the latest limitations and known issues.
## Version number
The version numbers of the PHP drivers follow [semantic versioning](https://semver.org/):
@ -88,7 +88,7 @@ The version number may have trailing pre-release version identifiers to indicate
- Build metadata may be denoted by a plus sign followed by 4 or 5 digits, such as `1.2.3-preview+5678` or `1.2.3+5678`. Build metadata does not figure into the precedence order.
## Future Plans
- Expand SQL Server 2016 feature support (example: Azure Active Directory)
- Expand SQL Server feature support (example: Azure Active Directory, Always Encrypted, etc.)
- Add more verification/fundamental tests
- Improve performance
- Bug fixes
@ -109,7 +109,7 @@ Thank you!
**Q:** What's next?
**A:** We will continue working on our future plans and releasing previews of upcoming [releases](https://github.com/Microsoft/msphpsql/releases)
**A:** We will continue working on our future plans and releasing previews of upcoming [releases][releases]
**Q:** Is Microsoft taking pull requests for this project?
@ -127,20 +127,24 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf
**Documentation**: [Microsoft Docs Online][phpdoc].
**Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog].
**SQL Server Drivers**: Please browse the articles for announcements of various [SQL Server Drivers][sqldrivers].
**Known Issues**: Please visit the [project on Github][project] to view outstanding [issues][issues] and report new ones.
[blog]: https://blogs.msdn.com/b/sqlphp/
[sqldrivers]: https://techcommunity.microsoft.com/t5/SQL-Server/bg-p/SQLServer/label-name/SQLServerDrivers
[project]: https://github.com/Microsoft/msphpsql
[issues]: https://github.com/Microsoft/msphpsql/issues
[releases]: https://github.com/microsoft/msphpsql/releases
[phpweb]: https://php.net
[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild
[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2
[phpdoc]: https://docs.microsoft.com/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017
[PHPMan]: https://php.net/manual/install.unix.php
[odbcdoc]: https://docs.microsoft.com/sql/connect/odbc/microsoft-odbc-driver-for-sql-server?view=sql-server-2017
[unixinstructions]: https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac

View file

@ -23,7 +23,7 @@ environment:
TEST_PHP_SQL_SERVER: (local)\SQL2017
SQL_INSTANCE: SQL2017
PHP_VC: 15
PHP_MAJOR_VER: 7.2
PHP_MAJOR_VER: 7.3
PHP_MINOR_VER: 11
PHP_EXE_PATH: x64\Release_TS
THREAD: ts
@ -39,7 +39,7 @@ environment:
THREAD: nts
platform: x86
# PHP_MAJOR_VER is PHP major version to build (7.2, 7.1)
# PHP_MAJOR_VER is PHP major version to build (7.2, 7.3)
# PHP_MINOR_VER is PHP point release number (or latest for latest release)
# PHP_VC is the Visual C++ version
# PHP_EXE_PATH is the relative path from php src folder to php executable
@ -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.1.1_x64.msi', 'c:\projects\msodbcsql_17.4.1.1_x64.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.4.1.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/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
- 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

@ -60,7 +60,7 @@ jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-18.04'
steps:
- checkout: self
clean: true
@ -85,7 +85,7 @@ jobs:
sudo apt-get purge unixodbc
sudo apt autoremove
sudo curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > mssql-release.list
curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list > mssql-release.list
sudo mv mssql-release.list /etc/apt/sources.list.d/
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install msodbcsql17 mssql-tools

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -520,7 +520,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
fetch_numeric( false ),
fetch_datetime( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES )
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED)
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
@ -720,13 +721,6 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch
driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size;
}
// if the user didn't set anything in the prepare options, then set the query timeout
// to the value set on the connection.
if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) {
core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC );
}
// rewrite named parameters in the query to positional parameters if we aren't letting PDO do the
// parameter substitution for us
if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
@ -1111,6 +1105,27 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
}
break;
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
if (Z_TYPE_P(val) != IS_LONG) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
zend_long value = Z_LVAL_P(val);
if (value == PDO_PARAM_STR_NATL) {
driver_dbh->use_national_characters = 1;
}
else if (value == PDO_PARAM_STR_CHAR) {
driver_dbh->use_national_characters = 0;
}
else {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
}
break;
#endif
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
@ -1282,6 +1297,14 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
break;
}
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
ZVAL_LONG(return_value, (driver_dbh->use_national_characters == 0) ? PDO_PARAM_STR_CHAR : PDO_PARAM_STR_NATL);
break;
}
#endif
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
@ -1432,14 +1455,18 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len,
enum pdo_param_type /*paramtype*/ TSRMLS_DC )
enum pdo_param_type paramtype TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
bool use_national_char_set = false;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>(dbh->driver_data);
SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL.");
// get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
// 1. PDO::quote() - object name is PDO
// 2. PDOStatement::execute() - object name is PDOStatement
@ -1468,13 +1495,12 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null");
if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) {
encoding = driver_stmt->encoding();
}
else {
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( stmt->driver_data );
encoding = driver_dbh->encoding();
encoding = driver_stmt->encoding();
if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) {
pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>(stmt->driver_data);
encoding = stmt_driver_dbh->encoding();
}
// get the placeholder at the current position in driver_stmt->placeholders ht
// Normally it's not a good idea to alter the internal pointer in a hashed array
// (see pull request 634 on GitHub) but in this case this is for internal use only
@ -1496,6 +1522,16 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
}
}
use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8);
#if PHP_VERSION_ID >= 70200
if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
use_national_char_set = true;
}
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
use_national_char_set = false;
}
#endif
if ( encoding == SQLSRV_ENCODING_BINARY ) {
// convert from char* to hex digits using os
std::basic_ostringstream<char> os;
@ -1540,7 +1576,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
// count the number of quotes needed
unsigned int quotes_needed = 2; // the initial start and end quotes of course
// include the N proceeding the initial quote if encoding is UTF8
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
if (use_national_char_set) {
quotes_needed = 3;
}
for ( size_t index = 0; index < unquoted_len; ++index ) {
@ -1554,7 +1590,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
unsigned int out_current = 0;
// insert N if the encoding is UTF8
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
if (use_national_char_set) {
( *quoted )[out_current++] = 'N';
}
// insert initial quote

View file

@ -580,6 +580,11 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC )
query_len = static_cast<unsigned int>(stmt->active_query_stringlen);
}
// The query timeout setting is inherited from the corresponding connection attribute, but
// the user may have changed the query timeout setting again before this via
// PDOStatement::setAttribute()
driver_stmt->set_query_timeout();
SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len );
if ( execReturn == SQL_NO_DATA ) {
@ -776,8 +781,12 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
"Invalid column number in pdo_sqlsrv_stmt_get_col_data" );
// set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast<SQLUINTEGER>( driver_stmt->current_meta_data[colno]->field_type ),
static_cast<SQLUINTEGER>( driver_stmt->current_meta_data[colno]->field_size ), true );
// save the php type for next use
sqlsrv_php_type = driver_stmt->sql_type_to_php_type(
static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
true);
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
// if a column is bound to a type different than the column type, figure out a way to convert it to the
// type they want
@ -820,6 +829,9 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
break;
}
}
// save the php type for the bound column
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
}
SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;
@ -1271,18 +1283,35 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
throw pdo::PDOException();
}
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
// vso 2829: derive the pdo_type for input/output parameter as well
// also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params
int pdo_type = param->param_type;
if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
direction = SQL_PARAM_INPUT_OUTPUT;
pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
}
else {
direction = SQL_PARAM_OUTPUT;
}
}
// check if the user has specified the character set to use, take it off but ignore
#if PHP_VERSION_ID >= 70200
if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
pdo_type = pdo_type & ~PDO_PARAM_STR_NATL;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored.");
}
if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored.");
}
#endif
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
// vso 2829: derive the pdo_type for input/output parameter as well
int pdo_type = (direction == SQL_PARAM_OUTPUT) ? param->param_type : param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
switch (pdo_type) {
case PDO_PARAM_BOOL:
@ -1349,13 +1378,17 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
throw pdo::PDOException();
}
// the encoding by default is that set on the statement
SQLSRV_ENCODING encoding = driver_stmt->encoding();
// if the statement's encoding is the default, then use the one on the connection
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = driver_stmt->conn->encoding();
}
// if the user provided an encoding, use it instead
// Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL
// But this extended type will be ignored in real prepared statements, so the encoding deliberately
// set in the statement or driver options will still take precedence
if( !Z_ISUNDEF(param->driver_params) ) {
CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
@ -1378,6 +1411,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
break;
}
}
// and bind the parameter
core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
sql_type, column_size, decimal_digits TSRMLS_CC );
@ -1503,3 +1537,11 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type,
return sqlsrv_phptype;
}
void pdo_sqlsrv_stmt::set_query_timeout()
{
if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) {
return;
}
core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>((SQLLEN)query_timeout), SQL_IS_UINTEGER TSRMLS_CC);
}

View file

@ -461,6 +461,10 @@ pdo_error PDO_ERRORS[] = {
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true}
},
{
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID,
{ IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false}
},
{ UINT_MAX, {} }
};

View file

@ -139,8 +139,8 @@ class conn_string_parser : private string_parser
int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len );
void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC);
protected:
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC);
protected:
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC);
public:
conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht );
@ -183,6 +183,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn {
bool fetch_datetime;
bool format_decimals;
short decimal_places;
short use_national_characters;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC );
};
@ -246,6 +247,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
fetch_datetime = db->fetch_datetime;
format_decimals = db->format_decimals;
decimal_places = db->decimal_places;
query_timeout = db->query_timeout;
}
virtual ~pdo_sqlsrv_stmt( void );
@ -254,6 +256,9 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
// for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types
virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream );
// driver specific way to set query timeout
virtual void set_query_timeout();
bool direct_query; // flag set if the query should be executed directly or prepared
const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters
size_t direct_query_subst_string_len; // length of query string used for direct queries
@ -382,7 +387,8 @@ enum PDO_ERROR_CODES {
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID
};
extern pdo_error PDO_ERRORS[];

View file

@ -718,14 +718,14 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v
const char *pch = strchr(pstr, '}');
size_t i = 0;
while (pch != NULL && i < value_len) {
i = pch - pstr + 1;
if (i == value_len || (i < value_len && pstr[i] != '}')) {
return false;
}
i++; // skip the brace
pch = strchr(pch + 2, '}'); // continue searching
}
@ -783,7 +783,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
try {
// Since connection options access token and authentication cannot coexist, check if both of them are used.
// If access token is specified, check UID and PWD as well.
// If access token is specified, check UID and PWD as well.
// No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers
if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) {
bool invalidOptions = false;
@ -801,7 +801,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
access_token_used = true;
}
// Check if Authentication is ActiveDirectoryMSI
// Check if Authentication is ActiveDirectoryMSI
// https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
bool activeDirectoryMSI = false;
if (authentication_option_used) {
@ -813,7 +813,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) {
activeDirectoryMSI = true;
// There are two types of managed identities:
// There are two types of managed identities:
// (1) A system-assigned managed identity: UID must be NULL
// (2) A user-assigned managed identity: UID defined but must not be an empty string
// In both cases, PWD must be NULL
@ -832,11 +832,11 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
}
}
}
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC );
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// because they are incompatible
if (!access_token_used && !activeDirectoryMSI) {
if (uid == NULL || strnlen_s(uid) == 0) {
@ -1153,9 +1153,12 @@ void column_encryption_set_func::func( _In_ connection_option const* option, _In
convert_to_string( value );
const char* value_str = Z_STRVAL_P( value );
// Column Encryption is disabled by default unless it is explicitly 'Enabled'
// Column Encryption is disabled by default, but if it is present and not
// explicitly set to disabled or enabled, the ODBC driver will assume the
// user is providing an attestation protocol and URL for enclave support.
// For our purposes we need only set ce_option.enabled to true if not disabled.
conn->ce_option.enabled = false;
if ( !stricmp(value_str, "enabled" )) {
if ( stricmp(value_str, "disabled" )) {
conn->ce_option.enabled = true;
}
@ -1200,7 +1203,7 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval*
char *pValue = static_cast<char*>(sqlsrv_malloc(value_len + 1));
memcpy_s(pValue, value_len + 1, value_str, value_len);
pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage
// This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once
if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) {
conn->ce_option.akv_id = pValue;
@ -1262,10 +1265,10 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva
}
const char* value_str = Z_STRVAL_P( value );
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure
//
// typedef struct AccessToken
@ -1276,30 +1279,30 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva
//
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows.
//
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// similar to a UCS-2 string containing only ASCII characters
//
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token
size_t dataSize = 2 * value_len;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
accToken = reinterpret_cast<ACCESSTOKEN*>(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize));
ACCESSTOKEN *pAccToken = accToken.get();
SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token.");
pAccToken->dataSize = dataSize;
// Expand access token with padding bytes
for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) {
pAccToken->data[i] = value_str[j];
pAccToken->data[i+1] = 0;
}
core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast<SQLPOINTER>(pAccToken), SQL_IS_POINTER);
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
conn->azure_ad_access_token = pAccToken;
accToken.transferred();
}

View file

@ -240,6 +240,9 @@ const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1;
// default value of decimal places (no formatting required)
const short NO_CHANGE_DECIMAL_PLACES = -1;
// default value for national character set strings (user did not specify any preference)
const short CHARSET_PREFERENCE_NOT_SPECIFIED = -1;
// buffer size allocated to retrieve data from a PHP stream. This number
// was chosen since PHP doesn't return more than 8k at a time even if
// the amount requested was more.
@ -1558,6 +1561,8 @@ struct sqlsrv_stmt : public sqlsrv_context {
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ) = 0;
// driver specific way to set query timeout
virtual void set_query_timeout() = 0;
};
// *** field metadata struct ***
@ -1571,15 +1576,23 @@ struct field_meta_data {
SQLSMALLINT field_scale;
SQLSMALLINT field_is_nullable;
bool field_is_money_type;
sqlsrv_phptype sqlsrv_php_type;
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
field_scale (0), field_is_nullable(0), field_is_money_type(false)
{
reset_php_type();
}
~field_meta_data()
{
}
void reset_php_type()
{
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
}
};
// *** statement constants ***
@ -1616,7 +1629,6 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true );
void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong paramno, zval* param_z TSRMLS_DC );
void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type TSRMLS_DC );
void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout TSRMLS_DC );
void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC );
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC );
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );

View file

@ -244,6 +244,12 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D )
// delete sensivity data
clean_up_sensitivity_metadata();
// reset sqlsrv php type in meta data
size_t num_fields = this->current_meta_data.size();
for (size_t f = 0; f < num_fields; f++) {
this->current_meta_data[f]->reset_php_type();
}
// create a new result set
if( cursor_type == SQLSRV_CURSOR_BUFFERED ) {
sqlsrv_malloc_auto_ptr<sqlsrv_buffered_result_set> result;
@ -322,6 +328,11 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
} ZEND_HASH_FOREACH_END();
}
// The query timeout setting is inherited from the corresponding connection attribute, but
// the user may override that the query timeout setting using the statement option.
// In any case, set query timeout using the latest value
stmt->set_query_timeout();
return_stmt = stmt;
stmt.transferred();
}
@ -1116,9 +1127,6 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
// Make sure that the statement was executed and not just prepared.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
@ -1127,37 +1135,47 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
// may also be retrieved.
if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) {
sqlsrv_phptype invalid;
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
SQLSRV_ASSERT( reinterpret_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." );
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC );
// delete the value returned since we only want it cached, not the actual value
if( field_value ) {
efree( field_value );
field_value = NULL;
*field_len = 0;
}
}
sqlsrv_phptype invalid;
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
SQLSRV_ASSERT( reinterpret_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." );
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC );
// delete the value returned since we only want it cached, not the actual value
if( field_value ) {
efree( field_value );
field_value = NULL;
*field_len = 0;
}
}
}
// If the php type was not specified set the php type to be the default type.
if (sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) {
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" );
sql_field_type = stmt->current_meta_data[field_index]->field_type;
if (stmt->current_meta_data[field_index]->field_precision > 0) {
sql_field_len = stmt->current_meta_data[field_index]->field_precision;
// Get the corresponding php type from the sql type and then save the result for later
if (stmt->current_meta_data[field_index]->sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) {
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
sql_field_type = stmt->current_meta_data[field_index]->field_type;
if (stmt->current_meta_data[field_index]->field_precision > 0) {
sql_field_len = stmt->current_meta_data[field_index]->field_precision;
}
else {
sql_field_len = stmt->current_meta_data[field_index]->field_size;
}
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string);
stmt->current_meta_data[field_index]->sqlsrv_php_type = sqlsrv_php_type;
}
else {
sql_field_len = stmt->current_meta_data[field_index]->field_size;
// use the previously saved php type
sqlsrv_php_type = stmt->current_meta_data[field_index]->sqlsrv_php_type;
}
// Get the corresponding php type from the sql type.
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string);
}
}
// Verify that we have an acceptable type to convert.
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(sqlsrv_php_type), stmt, SQLSRV_ERROR_INVALID_TYPE) {
throw core::CoreException();
}
@ -1361,7 +1379,7 @@ void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLE
}
// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout
// Extracts the long value and calls the core_sqlsrv_set_query_timeout
// which accepts timeout parameter as a long. If the zval is not of type long
// than throws error.
void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC )
@ -1375,37 +1393,8 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* val
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) );
}
core_sqlsrv_set_query_timeout( stmt, static_cast<long>( Z_LVAL_P( value_z )) TSRMLS_CC );
}
catch( core::CoreException& ) {
throw;
}
}
// Overloaded. Accepts the timeout as a long.
void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." );
// set the statement attribute
core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC );
// a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which
// is represented by -1.
int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ );
// set the LOCK_TIMEOUT on the server.
char lock_timeout_sql[32] = {'\0'};
int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout );
SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )),
"stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." );
core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC );
stmt->query_timeout = timeout;
// Save the query timeout setting for processing later
stmt->query_timeout = static_cast<long>(Z_LVAL_P(value_z));
}
catch( core::CoreException& ) {
throw;

View file

@ -27,7 +27,7 @@
// Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 7
#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

@ -124,6 +124,9 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt {
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream );
// driver specific way to set query timeout
virtual void set_query_timeout();
bool prepared; // whether the statement has been prepared yet (used for error messages)
zend_ulong conn_index; // index into the connection hash that contains this statement structure
zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute

View file

@ -267,6 +267,29 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _
return ss_phptype;
}
void ss_sqlsrv_stmt::set_query_timeout()
{
if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) {
return;
}
// set the statement attribute
core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( (SQLLEN)query_timeout ), SQL_IS_UINTEGER TSRMLS_CC );
// a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which
// is represented by -1.
int lock_timeout = (( query_timeout == 0 ) ? -1 : query_timeout * 1000 /*convert to milliseconds*/ );
// set the LOCK_TIMEOUT on the server.
char lock_timeout_sql[32] = {'\0'};
int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout );
SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )),
"stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." );
core::SQLExecDirect(this, lock_timeout_sql TSRMLS_CC );
}
// statement specific parameter proccessing. Uses the generic function specialised to return a statement
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \

View file

@ -0,0 +1,163 @@
<?php
include("MsSetup.inc");
$tableName = "aev2test";
// 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-enclave2", "CEK-win-noenclave", "CEK-win-noenclave2");
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave", "CEK-akv-noenclave");
$targetKeys = array("CEK-akv-enclave", "CEK-akv-enclave2", "CEK-akv-noenclave", "CEK-akv-noenclave2");
}
// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Randomized", "Deterministic");
$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.
// TODO: Add binary string types and fix smalldatetime issues.
$dataTypes = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
//'binary',
//'varbinary',
//'varbinary(max)',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
//'smalldatetime',
);
// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames = array();
$colNamesAE = array();
foreach ($dataTypes as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames[$type] = "c_".$column;
$colNamesAE[$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.000000001,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:30','1985-03-31 12:40:40','2025-07-23 05:00:32','1999-12-31 00:00:00',
'1956-02-27 23:59:59','2018-09-01 14:35:30','2079-06-06 23:59:29.997','1931-10-04 19:52:21');
// 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(0x3AD2BBC2, 720, 0xEED3A109F8F7745C, 0xD6C3E0E11A25F,0x4EACCEF38788F9,0xFFFFFFFFFFFFFFFF,6230974598,0x44E4A);
$testValues['varbinary'] = array(58342,0xF3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC888,0xE4300FF,0x000005ED309D3A45,0x06CADE379,804,0x00000000000000,0xD7209FFE4);
$testValues['varbinary(max)'] = array(0xEF409CB33408, 0xD3EA762C78F,0,0xFFFFFFFFFFFFFFFFFFFFFFFFF,
0x582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1F,
0x38054,9230094389109,0xDDD4D88C4B80089D2E4A,0x88F8A8);
// 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' => 0x44E4A,
'varbinary' => 0xE4300FF,
'varbinary(max)' => 0xD3EA762C78F,
'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:30',
);
// String patterns to test with LIKE
$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('0x44E4A'),
'varbinary' => array('0xE4300FF'),
'varbinary(max)' => array('0xD3EA762C78F'),
'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'),
);
?>

View file

@ -49,4 +49,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo
$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,120 @@
--TEST--
GitHub issue 1018 - Test emulate prepared statements with the extended string types
--DESCRIPTION--
This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and
PDO::PARAM_STR_CHAR will affect "emulate prepared" statements. If the parameter encoding is specified,
it also matters. The N'' prefix will be used when either it is PDO::PARAM_STR_NATL or the
parameter encoding is UTF-8.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_old_php.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
$p = 'Ĉǽŋ';
$p1 = 'C??';
function toEmulatePrepare($conn, $pdoStrParam, $value, $testCase, $utf8 = false)
{
global $p;
$sql = 'SELECT :value';
$options = array(PDO::ATTR_EMULATE_PREPARES => true);
$stmt = $conn->prepare($sql, $options);
if ($utf8) {
$stmt->bindParam(':value', $p, $pdoStrParam, 0, PDO::SQLSRV_ENCODING_UTF8);
} else {
$stmt->bindParam(':value', $p, $pdoStrParam);
}
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_NUM);
trace("$testCase: expected $value and returned $result[0]\n");
if ($result[0] !== $value) {
echo("$testCase: expected $value but returned:\n");
var_dump($result);
}
}
try {
$conn = connect();
// Test case 1: PDO::PARAM_STR_NATL
$testCase = 'Test case 1: no default but specifies PDO::PARAM_STR_NATL';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase);
// Test case 2: PDO::PARAM_STR_CHAR
$testCase = 'Test case 2: no default but specifies PDO::PARAM_STR_CHAR';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase);
// Test case 3: no extended string types
$testCase = 'Test case 3: no default but no extended string types either';
toEmulatePrepare($conn, PDO::PARAM_STR, $p1, $testCase);
// Test case 4: no extended string types but specifies UTF 8 encoding
$testCase = 'Test case 4: no default but no extended string types but with UTF-8';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true);
////////////////////////////////////////////////////////////////////////
// NEXT tests: set the default string type: PDO::PARAM_STR_CHAR first
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_CHAR);
// Test case 5: overrides the default PDO::PARAM_STR_CHAR
$testCase = 'Test case 5: overrides the default PDO::PARAM_STR_CHAR';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase);
// Test case 6: specifies PDO::PARAM_STR_CHAR directly
$testCase = 'Test case 6: specifies PDO::PARAM_STR_CHAR, same as the default';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase);
// Test case 7: uses the default PDO::PARAM_STR_CHAR without specifying
$testCase = 'Test case 7: no extended string types (uses the default)';
toEmulatePrepare($conn, PDO::PARAM_STR, $p1, $testCase);
// Test case 8: uses the default PDO::PARAM_STR_CHAR without specifying but with UTF 8 encoding
$testCase = 'Test case 8: no extended string types (uses the default) but with UTF-8 ';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true);
////////////////////////////////////////////////////////////////////////
// NEXT tests: set the default string type: PDO::PARAM_STR_NATL
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
// Test case 9: overrides the default PDO::PARAM_STR_NATL
$testCase = 'Test case 9: overrides the default PDO::PARAM_STR_NATL';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase);
// Test case 10: specifies PDO::PARAM_STR_NATL directly
$testCase = 'Test case 10: specifies PDO::PARAM_STR_NATL, same as the default';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase);
// Test case 11: uses the default PDO::PARAM_STR_NATL without specifying
$testCase = 'Test case 11: no extended string types (uses the default)';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase);
// Test case 12: uses the default PDO::PARAM_STR_NATL without specifying but with UTF 8 encoding
$testCase = 'Test case 12: no extended string types (uses the default) but with UTF-8';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true);
echo "Done\n";
} catch (PdoException $e) {
if (isAEConnected()) {
// The Always Encrypted feature does not support emulate prepare for binding parameters
$expected = '*Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.';
if (!fnmatch($expected, $e->getMessage())) {
echo "Unexpected exception caught when connecting with Column Encryption enabled:\n";
echo $e->getMessage() . PHP_EOL;
} else {
echo "Done\n";
}
} else {
echo $e->getMessage() . PHP_EOL;
}
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,93 @@
--TEST--
GitHub issue 1018 - Test PDO::quote() with the extended string types
--DESCRIPTION--
This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and
PDO::PARAM_STR_CHAR will affect how PDO::quote() works.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_old_php.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function testErrorCase2($conn, $isChar)
{
try {
$stmt = $conn->query('select 1');
$error = '*An invalid attribute was designated on the PDOStatement object.';
$pdoParam = ($isChar) ? PDO::PARAM_STR_CHAR : PDO::PARAM_STR_NATL;
// This will cause an exception because PDO::ATTR_DEFAULT_STR_PARAM is not a statement attribute
$stmt->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, $pdoParam);
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Unexpected error returned setting PDO::ATTR_DEFAULT_STR_PARAM on statement\n";
var_dump($e->getMessage());
}
}
}
function testErrorCase($attr)
{
try {
$conn = connect();
$error = '*Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.';
// This will cause an exception because PDO::ATTR_DEFAULT_STR_PARAM expects either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL only
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, $attr);
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Unexpected error returned setting PDO::ATTR_DEFAULT_STR_PARAM\n";
var_dump($e->getMessage());
}
}
}
try {
testErrorCase(true);
testErrorCase('abc');
testErrorCase(4);
$conn = connect();
testErrorCase2($conn, true);
testErrorCase2($conn, false);
// Start testing quote function
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_CHAR);
var_dump($conn->quote(null, PDO::PARAM_NULL));
var_dump($conn->quote('\'', PDO::PARAM_STR));
var_dump($conn->quote('foo', PDO::PARAM_STR));
var_dump($conn->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
var_dump($conn->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
var_dump($conn->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR);
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
var_dump($conn->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
var_dump($conn->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
var_dump($conn->quote('über', PDO::PARAM_STR));
var_dump($conn->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
unset($conn);
echo "Done\n";
} catch (PDOException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
string(2) "''"
string(4) "''''"
string(5) "'foo'"
string(5) "'foo'"
string(8) "N'über'"
bool(true)
bool(true)
string(5) "'foo'"
string(8) "N'über'"
string(8) "N'über'"
Done

View file

@ -0,0 +1,131 @@
--TEST--
GitHub issue 1018 - Test real prepared statements with the extended string types
--DESCRIPTION--
This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and
PDO::PARAM_STR_CHAR will NOT affect real prepared statements. Unlike emulate prepared statements,
real prepared statements will only be affected by the parameter encoding. If not set, it will use
the statement encoding or the connection one, which is by default UTF-8.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_old_php.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
$p = '銀河galaxy';
$p1 = '??galaxy';
$tableName = 'test1018';
function insertRead($conn, $pdoStrParam, $value, $testCase, $id, $encoding = false)
{
global $p, $tableName;
$sql = "INSERT INTO $tableName (Col1) VALUES (:value)";
$options = array(PDO::ATTR_EMULATE_PREPARES => false); // it's false by default anyway
$stmt = $conn->prepare($sql, $options);
// Set param encoding only if $encoding is NOT FALSE
if ($encoding !== false) {
$stmt->bindParam(':value', $p, $pdoStrParam, 0, $encoding);
$encOptions = array(PDO::SQLSRV_ATTR_ENCODING => $encoding);
} else {
$stmt->bindParam(':value', $p, $pdoStrParam);
$encOptions = array();
}
$stmt->execute();
// Should also set statement encoding when $encoding is NOT FALSE
// such that data can be fetched with the right encoding
$sql = "SELECT Col1 FROM $tableName WHERE ID = $id";
$stmt = $conn->prepare($sql, $encOptions);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_NUM);
trace("$testCase: expected $value and returned $result[0]\n");
if ($result[0] !== $value) {
echo("$testCase: expected $value but returned:\n");
var_dump($result);
}
}
function testUTF8encoding($conn)
{
global $p, $tableName;
// Create a NVARCHAR column
$sql = "CREATE TABLE $tableName (ID int identity(1,1), Col1 NVARCHAR(100))";
$conn->query($sql);
// The extended string types PDO::PARAM_STR_NATL and PDO::PARAM_STR_CHAR
// will be ignored in the following test cases. Only the statement or
// the connection encoding matters.
// Test case 1: PDO::PARAM_STR_CHAR
$testCase = 'UTF-8 case 1: no default but specifies PDO::PARAM_STR_CHAR';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p, $testCase, 1);
// Test case 2: PDO::PARAM_STR_NATL
$testCase = 'UTF-8 case 2: no default but specifies PDO::PARAM_STR_NATL';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase, 2);
// Test case 3: no extended string types
$testCase = 'UTF-8 case 3: no default but no extended string types either';
insertRead($conn, PDO::PARAM_STR, $p, $testCase, 3);
// Test case 4: no extended string types but specifies UTF-8 encoding
$testCase = 'UTF-8 case 4: no default but no extended string types but with UTF-8 encoding';
insertRead($conn, PDO::PARAM_STR, $p, $testCase, 4, PDO::SQLSRV_ENCODING_UTF8);
dropTable($conn, $tableName);
}
function testNonUTF8encoding($conn)
{
global $p, $p1, $tableName;
// Create a VARCHAR column
$sql = "CREATE TABLE $tableName (ID int identity(1,1), Col1 VARCHAR(100))";
$conn->query($sql);
// The extended string types PDO::PARAM_STR_NATL and PDO::PARAM_STR_CHAR
// will be ignored in the following test cases. Only the statement or
// the connection encoding matters.
// Test case 1: PDO::PARAM_STR_CHAR (expect $p1)
$testCase = 'System case 1: no default but specifies PDO::PARAM_STR_CHAR';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase, 1);
// Test case 2: PDO::PARAM_STR_NATL (expect $p1)
$testCase = 'System case 2: no default but specifies PDO::PARAM_STR_NATL';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p1, $testCase, 2);
// Test case 3: no extended string types (expect $p1)
$testCase = 'System case 3: no default but no extended string types either';
insertRead($conn, PDO::PARAM_STR, $p1, $testCase, 3);
// Test case 4: no extended string types but specifies UTF-8 encoding (expect $p1)
$testCase = 'System case 4: no default but no extended string types but with UTF-8 encoding';
insertRead($conn, PDO::PARAM_STR, $p1, $testCase, 4, PDO::SQLSRV_ENCODING_UTF8);
dropTable($conn, $tableName);
}
try {
$conn = connect();
dropTable($conn, $tableName);
// The connection encoding is by default PDO::SQLSRV_ENCODING_UTF8. For this test
// no change is made to the connection encoding.
testUTF8encoding($conn);
testNonUTF8encoding($conn);
echo "Done\n";
} catch (PdoException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,198 @@
--TEST--
GitHub issue 1027 - PDO::SQLSRV_ATTR_QUERY_TIMEOUT had no effect on PDO::exec()
--DESCRIPTION--
This test verifies that setting PDO::SQLSRV_ATTR_QUERY_TIMEOUT correctly should affect PDO::exec() as in the case for PDO::prepare() (as statement attribute or option).
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
const _DELAY = 5;
$message = '*Invalid value timeout specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.';
$delay = _DELAY;
$query = "WAITFOR DELAY '00:00:$delay'; SELECT 1";
$error = '*Query timeout expired';
function testTimeoutAttribute($conn, $timeout, $statementLevel = false)
{
global $message;
$invalid = str_replace('timeout', $timeout, $message);
try {
if ($statementLevel) {
trace("statement option expects error: $invalid\n");
$options = array(PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $timeout);
$sql = 'SELECT 1';
$stmt = $conn->prepare($sql, $options);
} else {
trace("connection attribute expects error: $invalid\n");
$conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout);
}
} catch (PDOException $e) {
if (!fnmatch($invalid, $e->getMessage())) {
echo "Unexpected error returned setting invalid $timeout for SQLSRV_ATTR_QUERY_TIMEOUT\n";
var_dump($e->getMessage());
}
}
}
function testErrors($conn)
{
testTimeoutAttribute($conn, 1.8);
testTimeoutAttribute($conn, 'xyz');
testTimeoutAttribute($conn, -99, true);
testTimeoutAttribute($conn, 'abc', true);
}
function checkTimeElapsed($message, $t0, $t1, $expectedDelay)
{
$elapsed = $t1 - $t0;
$diff = abs($elapsed - $expectedDelay);
$leeway = 1.0;
$missed = ($diff > $leeway);
trace("$message $elapsed secs elapsed\n");
if ($missed) {
echo $message;
echo "Expected $expectedDelay but $elapsed secs elapsed\n";
}
}
function connectionTest($timeout, $asAttribute)
{
global $query, $error;
$keyword = '';
if ($asAttribute) {
$conn = connect($keyword);
$conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout);
} else {
$options = array(PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $timeout);
$conn = connect($keyword, $options);
}
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// if timeout is 0 it means no timeout
$delay = ($timeout > 0) ? $timeout : _DELAY;
$result = null;
$t0 = microtime(true);
try {
$result = $conn->exec($query);
if ($timeout > 0) {
echo "connectionTest $timeout, $asAttribute: ";
echo "this should have timed out!\n";
}
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Connection test error expected $timeout, $asAttribute:\n";
var_dump($e->getMessage());
}
}
$t1 = microtime(true);
checkTimeElapsed("connectionTest ($timeout, $asAttribute): ", $t0, $t1, $delay);
return $conn;
}
function queryTest($conn, $timeout)
{
global $query, $error;
// if timeout is 0 it means no timeout
$delay = ($timeout > 0) ? $timeout : _DELAY;
$t0 = microtime(true);
try {
$conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout);
$stmt = $conn->query($query);
if ($timeout > 0) {
echo "Query test $timeout: should have timed out!\n";
}
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Query test error expected $timeout:\n";
var_dump($e->getMessage());
}
}
$t1 = microtime(true);
checkTimeElapsed("Query test ($timeout): ", $t0, $t1, $delay);
unset($stmt);
}
function statementTest($conn, $timeout, $asAttribute)
{
global $query, $error;
// if timeout is 0 it means no timeout
$delay = ($timeout > 0) ? $timeout : _DELAY;
$result = null;
$t0 = microtime(true);
try {
if ($asAttribute) {
$stmt = $conn->prepare($query);
$stmt->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout);
} else {
$options = array(PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $timeout);
$stmt = $conn->prepare($query, $options);
}
$result = $stmt->execute();
if ($timeout > 0) {
echo "statementTest $timeout: should have timed out!\n";
}
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Statement test error expected $timeout, $asAttribute:\n";
var_dump($e->getMessage());
}
}
$t1 = microtime(true);
checkTimeElapsed("statementTest ($timeout, $asAttribute): ", $t0, $t1, $delay);
unset($stmt);
}
try {
$rand = rand(1, 100);
$timeout = $rand % 3;
$asAttribute = $rand % 2;
$conn = connectionTest($timeout, $asAttribute);
testErrors($conn);
unset($conn);
$conn = connectionTest(0, !$asAttribute);
queryTest($conn, $timeout);
for ($i = 0; $i < 2; $i++) {
statementTest($conn, $timeout, $i);
}
unset($conn);
echo "Done\n";
} catch (PdoException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,81 @@
--TEST--
GitHub issue #569 - direct query on varchar max fields results in function sequence error (Always Encrypted)
--DESCRIPTION--
This is similar to pdo_569_query_varcharmax.phpt but is not limited to testing the Always Encrypted feature in Windows only.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tableName = 'pdoTestTable_569_ae';
createTable($conn, $tableName, array(new ColumnMeta("int", "id", "IDENTITY"), "c1" => "nvarchar(max)"));
$input = array();
$input[0] = 'some very large string';
$input[1] = '1234567890.1234';
$input[2] = 'über über';
$numRows = 3;
$tsql = "INSERT INTO $tableName (c1) VALUES (?)";
$stmt = $conn->prepare($tsql);
for ($i = 0; $i < $numRows; $i++) {
$stmt->bindParam(1, $input[$i]);
$stmt->execute();
}
$tsql = "SELECT id, c1 FROM $tableName ORDER BY id";
$stmt = $conn->prepare($tsql);
$stmt->execute();
// Fetch one row each time with different pdo type and/or encoding
$result = $stmt->fetch(PDO::FETCH_NUM);
if ($result[1] !== $input[0]) {
echo "Expected $input[0] but got: ";
var_dump($result[0]);
}
$stmt->bindColumn(2, $value, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_SYSTEM);
$result = $stmt->fetch(PDO::FETCH_BOUND);
if (!$result || $value !== $input[1]) {
echo "Expected $input[1] but got: ";
var_dump($result);
}
$stmt->bindColumn(2, $value, PDO::PARAM_STR);
$result = $stmt->fetch(PDO::FETCH_BOUND);
if (!$result || $value !== $input[2]) {
echo "Expected $input[2] but got: ";
var_dump($value);
}
// Fetch again but all at once
$stmt->execute();
$rows = $stmt->fetchall(PDO::FETCH_ASSOC);
for ($i = 0; $i < $numRows; $i++) {
$i = $rows[$i]['id'] - 1;
if ($rows[$i]['c1'] !== $input[$i]) {
echo "Expected $input[$i] but got: ";
var_dump($rows[$i]['c1']);
}
}
unset($stmt);
unset($conn);
} catch (PDOException $e) {
echo $e->getMessage();
}
echo "Done\n";
?>
--EXPECT--
Done

View file

@ -0,0 +1,488 @@
<?php
// Connect and clear the procedure cache
function connect($server, $attestation_info)
{
include("MsSetup.inc");
$options = "sqlsrv:Server=$server;Database=$databaseName;ColumnEncryption=$attestation_info";
if ($keystore == 'akv') {
$security_info = '';
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$security_info .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$security_info .= ";KeyStorePrincipalId=$AKVPrincipalName";
$security_info .= ";KeyStoreSecret=$AKVPassword";
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$security_info .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$security_info .= ";KeyStorePrincipalId=$AKVClientID";
$security_info .= ";KeyStoreSecret=$AKVSecret";
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options .= $security_info;
}
$conn = new PDO($options, $uid, $pwd);
$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);
$info = $stmt->fetch();
if ($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");
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 query can be used to both encrypt plaintext
// columns and to re-encrypt encrypted columns.
// 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)
{
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
foreach ($dataTypes as $type) {
$insertValues[] = $testValues[$type][$v];
$insertValues[] = $testValues[$type][$v];
}
// Insert the data using PDO::prepare()
try {
$stmt = $conn->prepare($insertQuery);
$stmt->execute($insertValues);
} catch (PDOException $error) {
print_r($error);
die("Inserting values in encrypted table failed\n");
}
}
}
// 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, $comparison='', $type='')
{
try {
$nonAEstmt->execute();
} catch(Exception $error) {
print_r($error);
die("Executing non-AE statement failed!\n");
}
try {
$AEstmt->execute();
} catch(Exception $error) {
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r($error);
die("Equality comparison failed for deterministic encryption!\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!");
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
$e = $error->errorInfo;
die("Equality comparison failed for deterministic encryption!\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!");
}
} 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!\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!\n");
}
} else {
print_r($error);
die("Unexpected error occurred in compareResults!\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') {
// 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
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, $attestation)
{
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);
$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);
$AEstmt->bindParam(1, $thresholds[$type], $PDOType);
$nonAEstmt = $conn->prepare($nonAEQuery);
$nonAEstmt->bindParam(1, $thresholds[$type], $PDOType);
} catch (PDOException $error) {
print_r($error);
die("Preparing/binding statements for comparison failed");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison, $type);
}
}
}
// 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: Patterns to match against, 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', or 'wrongurl'
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation)
{
foreach ($dataTypes as $type) {
// TODO: Pattern matching doesn't work in AE for non-string types
// without an explicit cast
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 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).
$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;
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");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $pattern, $type);
}
}
}
}
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, ["binary", "varbinary", "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 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

@ -48,6 +48,26 @@ $dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nva
$tableName = "akv_comparison_table";
// First determine if the server is AE v2 enabled
$isEnclaveEnabled = false;
$connectionOptions = "sqlsrv:Server=$server;Database=$databaseName";
$conn = new PDO($connectionOptions, $uid, $pwd);
if (!$conn) {
fatalError("Initial connection failed\n");
} else {
$query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';";
$stmt = $conn->query($query);
$info = $stmt->fetch();
if ($info['value'] == 1 and $info['value_in_use'] == 1) {
$isEnclaveEnabled = true;
}
$conn->query("DBCC FREEPROCCACHE");
}
unset($conn);
// Test every combination of the keywords above.
// Leave out good credentials to ensure that caching does not influence the
// results. The cache timeout can only be changed with SQLSetConnectAttr, so
@ -117,8 +137,11 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
unset($stmt);
} else {
// The INSERT query succeeded with bad credentials, which
// should only happen when encryption is not enabled.
if (isColEncrypted()) {
// should only happen when 1. encryption is not enabled or
// 2. when ColumnEncryption is set to something other than
// enabled or disabled (i.e. $i == 2), and the server is
// not enclave-enabled
if (!(!isColEncrypted() or ($i == 2 and !$isEnclaveEnabled))) {
fatalError("Successful insertion with bad credentials\n");
}
}
@ -135,6 +158,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
$errors,
array('CE258', '0'),
array('CE275', '0'),
array('CE400', '0'),
array('IMSSP', '-85'),
array('IMSSP', '-86'),
array('IMSSP', '-87'),
@ -147,6 +171,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
$errors,
array('CE258', '0'),
array('CE275', '0'),
array('CE400', '0'),
array('IMSSP', '-85'),
array('IMSSP', '-86'),
array('IMSSP', '-87'),

View file

@ -0,0 +1,93 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to 'enabled', which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $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. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with ColumnEncryption set to 'enabled'.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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 ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create 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!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with ColumnEncryption set to 'enabled'
$newAttestation = 'enabled';
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'enabled');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'enabled');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
try {
$stmt = $conn->query($alterQuery);
// Query should fail and trigger catch block before getting here
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
} catch (PDOException $error) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33546'));
}
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,136 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $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 cycles through $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. Re-encrypt the table using new key and/or encryption type.
7. Compare computations as in 4. above.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// 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 table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
// 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);
$encryptionFailed = false;
try {
$stmt = $conn->query($alterQuery);
if (!isEnclaveEnabled($key)) {
die("Encrypting should have failed with key $key and encryption type $encryptionType\n");
}
} catch (PDOException $error) {
if (!isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r($error);
die("Encrypting failed when it shouldn't have!\n");
}
}
}
}
if ($encryptionFailed) continue;
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// Try re-encrypting the table
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$encryptionFailed = false;
try {
$stmt = $conn->query($alterQuery);
if (!isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
}
} catch (Exception $error) {
if (!isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r($error);
die("Encrypting failed when it shouldn't have!\n");
}
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,60 @@
--TEST--
Test various settings for the ColumnEncryption keyword.
--DESCRIPTION--
For AE v2, the Column Encryption keyword must be set to [protocol]/[attestation URL].
If [protocol] is wrong, connection should fail; if the URL is wrong, connection
should succeed. This test sets ColumnEncryption to three values:
1. Random nonsense, which is interpreted as an incorrect protocol
so connection should fail.
2. Incorrect protocol with a correct attestation URL, connection should fail.
3. Correct protocol and incorrect URL, connection should succeed.
--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");
// Test with random nonsense. Connection should fail.
$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=xyz";
try {
$conn = new PDO($options, $uid, $pwd);
die("Connection should have failed!\n");
} catch(PDOException $error) {
$e = $error->errorInfo;
checkErrors($e, array('CE400', '0'));
}
// Test with incorrect protocol and good attestation URL. Connection should fail.
// Insert a rogue 'x' into the protocol part of the attestation.
$comma = strpos($attestation, ',');
$badProtocol = substr_replace($attestation, 'x', $comma, 0);
$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=$badProtocol";
try {
$conn = new PDO($options, $uid, $pwd);
die("Connection should have failed!\n");
} catch(Exception $error) {
$e = $error->errorInfo;
checkErrors($e, array('CE400', '0'));
}
// Test with good protocol and incorrect attestation URL. Connection should succeed
// because the URL is only checked when an enclave computation is attempted.
$badURL = substr_replace($attestation, 'x', $comma+1, 0);
$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=$badURL";
try {
$conn = new PDO($options, $uid, $pwd);
} catch(Exception $error) {
print_r($error);
die("Connecting with a bad attestation URL should have succeeded!\n");
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,109 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $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. Re-encrypt the table using new key and/or encryption type.
6. Compare computations as in 4. above.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->query("DBCC FREEPROCCACHE");
// Create 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!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// 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);
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
try {
$stmt = $conn->query($alterQuery);
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $encryptionType\n");
}
} catch (PDOException $error) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r($error);
die("Encrypting failed when it shouldn't have! key = $targetKey and type = $targetType\n");
}
continue;
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,95 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to the wrong attestation URL, which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $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. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with a faulty attestation URL.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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 ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create 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!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with a faulty attestation URL
$comma = strpos($attestation, ',');
$newAttestation = substr_replace($attestation, 'x', $comma+1, 0);
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'wrongurl');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'wrongurl');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
try {
$stmt = $conn->query($alterQuery);
// Query should fail and trigger catch block before getting here
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
} catch(Exception $error) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('CE405', '0'));
}
}
}
}
}
}
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,10 @@
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 70200) {
die("skip This test requires 7.2 or above");
}
?>

Binary file not shown.

View file

@ -1,35 +1,98 @@
/* DROP Column Encryption Key first, Column Master Key cannot be dropped until no encryption depends on it */
IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%')
/* DROP Column Encryption Keys first, Column Master Keys cannot be dropped until no CEKs depend on them */
IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%' OR [name] LIKE '%-win-%')
BEGIN
DROP COLUMN ENCRYPTION KEY [AEColumnKey]
DROP COLUMN ENCRYPTION KEY [CEK-win-enclave]
DROP COLUMN ENCRYPTION KEY [CEK-win-enclave2]
DROP COLUMN ENCRYPTION KEY [CEK-win-noenclave]
DROP COLUMN ENCRYPTION KEY [CEK-win-noenclave2]
END
GO
/* Can finally drop Column Master Key after the Encryption Key is dropped */
IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%')
/* Can finally drop Column Master Keys after the Column Encryption Keys are dropped */
IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%' OR [name] LIKE '%-win-%')
BEGIN
DROP COLUMN MASTER KEY [AEMasterKey]
DROP COLUMN MASTER KEY [CMK-win-enclave]
DROP COLUMN MASTER KEY [CMK-win-noenclave]
END
GO
/* Recreate the Column Master Key */
/* Create the Column Master Keys */
/* AKVMasterKey is a non-enclave enabled key for AE v1 testing */
/* The enclave-enabled master key requires an ENCLAVE_COMPUTATIONS clause */
CREATE COLUMN MASTER KEY [AEMasterKey]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816'
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816'
)
GO
/* Create Column Encryption Key using the Column Master Key */
/* The enclave-enabled master key requires an ENCLAVE_COMPUTATIONS clause */
CREATE COLUMN MASTER KEY [CMK-win-enclave]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/D9C0572FA54B221D6591C473BAEA53FE61AAC854',
ENCLAVE_COMPUTATIONS (SIGNATURE = 0xA1150DE565E9C132D2AAB8FF8B228EAA8DA804F250B5B422874CB608A3B274DDE523E71B655A3EFC6C3018B632701E9205BAD80C178614E1FE821C6807B0E70BCF11168FC4B202638905C5F016EDBADACA23C696B79772C56825F36EB8C0366B130C91D85362E560C9D2FDD20DCAE99619256045CA2725DEC9E0C115CAEB9EA686CCB0DE0D53D2056C01752B17B634FC6DBB51EA043F607349489722DB8A086CBC876649284A8352822DD22B328E7BA3D671CCDF54CDAAF61DFD6AF2EAAC14E03897324234AB103C45AB48131C1CD19040782359FC920A0AF61BA9842ADFB76C3196CBC6EB9C0A679926ED63E092B7C8643232C97A64C7F918104C210787A56F)
)
GO
CREATE COLUMN MASTER KEY [CMK-win-noenclave]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/D9C0572FA54B221D6591C473BAEA53FE61AAC854'
)
GO
/* Now we can create the Column Encryption Keys */
/* ENCRYPTED_VALUE is generated by SSMS and it is always the same if the same Certificate is imported */
CREATE COLUMN ENCRYPTION KEY [AEColumnKey]
WITH VALUES
(
COLUMN_MASTER_KEY = [AEMasterKey],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E
COLUMN_MASTER_KEY = [AEMasterKey],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E
)
GO
GO
/* There are two enclave enabled keys and two non-enclave enabled keys to test the case where a user
tries to reencrypt a table from one enclave enabled key to another enclave enabled key, or from a
non-enclave key to another non-enclave key */
CREATE COLUMN ENCRYPTION KEY [CEK-win-enclave]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-enclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034007382EDDDE3FFCE076D5715B6BBBD22EA64E665899BEFAAD5B329F218EE30BE9F789EB98717B6FD9E50AE496AC9FEED962B23442D4FD3FBFEC9C9B65F40A3BCEC7CFAC198F4CAEE8A255F67988289EF050F9F75D0287F3DF9A9FDA0C674E48DF2CB13298AAAD039930DD909EEE71682CC8A90202D3F2A1F1037BB20B1954C8B6A11F05D104CA9DAF1561C6B2F9DBB08BCE17244157B751C02FC1730E387F372C31327F2834D19AF626D0B46B152615F05FA2F3566350312CDE6DE1160B3C1D0FD35FAF13891C04711DF184DA501AA51D16BF009EA71A2D28E201804C6F8F9100E90234923B2713EA7988861FBA4E292E5518FFC02CCBD2513EDA871F6E03ECDDD309619557277C10A07906E55BA3F59A6A18834B4CD5185DA4B4574A18B8B1AC53A2C36B033D7A72443F1438E76E37306A1F92AC30BC751F6D7ED1633FEE807440E1D6096C53C5E3E33828C9C59E8761E5BAD341C6D9E2BD1F2B5C3992666620CAA38C4645C154976EF62AE80161A9F7700C96875A72995E1C585918B28F65060F1B8B96417328F6DEDFCA79ED9F01EAB19FF4E3163F9963BA26E9B58031A04320CC73702A6ED438513E0F8ABA1966B53114038CC587050F90D9CD0F9E26CA9749723ABA85CF31F963A5E85E04993B2B2869725E734BE8FCFD30A801825582730B49C00A2058C02D3312D6D8E82078FF4F77C5FF9CE6E9D140F1A4517635AB784
)
GO
CREATE COLUMN ENCRYPTION KEY [CEK-win-enclave2]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-enclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034006B4D40ABF0975AF7C5CA7D1F4345DE437318556F5A2380DCFE4AB792DC3A424EABC80EA24EE850FACD94F04809C8B32674C6FF2D966FA7F9F9E522990E5F5011515BA4B7EF3603619D8A4BF46AA9B769A8A4417462C4B0303F995F04964A2E328A503D87CD1AB85ECFCB8241D0C815540989DC33E58EDCCBAFF0753E196813E3FCCC5A3C9E4277DD528AE276F1F795973A4DF8D1BB3B1F405B5F35A6A583F0BB86BAD7FCADC1FCF6B14B602890109360FAB67D6A27DE542AE87784C40FEB9071AC34C4C40C92A6C153A4A38B6DA3AD48ED39E32D6D161ACE7EFE516B414139A831D878C13FF178649823C4EFDC8E5DB4C02F2147CC76965C01C2F3624EB809FD4F5C2E291056077B1ABEFF1F5001C1F4248704C7C70CF63DA1EBC2FEC4A3DF919BA4F6B465819BC4587599C2E7499CDE62D7C335CE7BBCFC72242A8F41C1B5C94DEB0A9AF49B723759A8CD9751EE70DDEBAFA1957382287F621790543841EBCCA0007BA030CAF29E9FBF8CEB4FEC88673F47B5EC3B5F759BBDD8ED2EAF572711D78286E4294B89FF6EBFEE4968B4596AF3B5C34985F28E886F6C211F385326F10ED62602007589FC494372902FB32B0E3D67A8C64F43A87B06EE9F2CF074EB6F3EC7A431733EDA8745051B7A4AA4C020797A9492E6A3BA643D031E491497BF17539993871085AC249D0AD82203CD442F69D6C686D26F4D17BA46B69D3CB7E395
)
GO
CREATE COLUMN ENCRYPTION KEY [CEK-win-noenclave]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-noenclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00640039006300300035003700320066006100350034006200320032003100640036003500390031006300340037003300620061006500610035003300660065003600310061006100630038003500340042DC7A3AAAD184E01288C0913EFB6FEC6167CD8EA08A5F46ADCCC34D3AA6A1BDDDA15EA3DD219ED8795AB05C0111E48EA35A82ADDF2A206FACBBF4FD73D01C004DF627012D3950FEBCA4BBEDBDF97BA77033728D8873BA81E1C7BDCBE04BB3AA7EB42A1EDDBEF9B1CA9477ADA33F76711FEDF782CA1BD3C0104FDEB9E0D66DFCEC7D3C236906481B44F04457549658635322447742FB00B6D6F36A7CFCC56BB39F7280736BC25FD499F9CBA2F63CE11D53E536FD4A266929E06CF2BDBAF229894A77EDE140323B674ECF28C58C3E0B6C2E9407AD1A26776CB55D68B8286F64787CE5A468CFA27295D6069EFA5D65CD9A04602E861F4504F2611AAE6A8ADE33038A2BECE8BD7CF5B48567C217E324F11935C552FD25FE1FEFB152684BD1B3F8EB70EC9F6439340CE82CD8E74DD5986A6C4F9E8336ED4AC804FAD800A3EA324F78DCE37832035C3DC92782A06150916D01322A80767D1A36D7A8D9BCF6727DCE6AC67A168FA8B8B5032E60DCB178B21A860F2D98BE09DA9BA5DCCBD0D339369FF3C50C7993463372CF5B1DA9FAA12CD16E76F5961C01EADC5804C7F22227E2095BAD0F90A47B6330B1B43407E01DE5B61CEBD542A93797428AD84376E9362EADE6DDD103B9EC96E616A2ECED7D1D665B5B872E77FC024AD92AB4A8335D12D41BDD152790E87590798C1005956F9F92D4DD0C1C9852D147F7CB55B3224DE8EF593F
)
GO
CREATE COLUMN ENCRYPTION KEY [CEK-win-noenclave2]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-noenclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034009014CD16FC878CEA2DE91C8C681AE86C7C062D8BD88C4CEE501A89FEAC47356D7181644A350F72B5F6023DA2B9E26C5A2522C08B1910D390068CF26794F4BA7B0298A6676B4DC6DED913E3B077B56224D2E1A3FE4EF33F58FE44CFC3DD67E54FB15BE8E29ABAF8357F378FBEDA3EBF9868A54746074D5E0E798047867E1ABD39AD0645BB8E071C72BFC37C007CBFC58F5690A5253F444E77169B2FE92FD95897A412B2078DA3804A00723D6DF824FCA527208A1DFB377B5BA16B620213F8252E10E7D7A3719A3FBB2F7A8189792B0BCF737236963C7DDCA6366F7B04F127925A1F8DDBB1B5A01D280BD300ECA3B1F31F24C8A0D517AE7BCBC3233A24E83B70A334754098DE373A1C027A4D09BB1D26C930E7501EB02464C519D19CFA0B296238AF11638C2E0688C7599E3DB1714AACF4EBFCEF63E1EE521A8E38E3BEFD4EF4991A15E8DD5CFD94E58E68754F3E90BC117025C01562F6440417A42612BE9C8871A18108CBE3E96DA7E35C45171C03E1DFBB3CA1E35A6D322F2D5B79E2BF2A07F14136DA4A768E08E2A7F1A42E04B717CB6AE3D1A3FA0EACCFC9CEC27DB53761E13DE1F55B410A65FB441D50CF8B2153B64925B1CEBDE062B5CAF4C99C41FED6836327037C46515710F16DC611305A0EBA1943A9BA5CC6889626990879713E9C95BB54D6A8A3C1C05A10AFE142B2487A1F0A07B57841E940CC9816E3F43CAE3CB7
)
GO

View file

@ -31,6 +31,8 @@ def setupAE(conn_options, dbname):
# import self signed certificate
inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot"
executeCommmand(inst_command)
inst_command = "certutil -user -p '' -importPFX My AEV2Cert.pfx NoRoot"
executeCommmand(inst_command)
# create Column Master Key and Column Encryption Key
script_command = 'sqlcmd -I ' + conn_options + ' -i ae_keys.sql -d ' + dbname
executeCommmand(script_command)

View file

@ -0,0 +1,163 @@
<?php
include("MsSetup.inc");
$tableName = "aev2test";
// 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-enclave2", "CEK-win-noenclave", "CEK-win-noenclave2");
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave", "CEK-akv-noenclave");
$targetKeys = array("CEK-akv-enclave", "CEK-akv-enclave2", "CEK-akv-noenclave", "CEK-akv-noenclave2");
}
// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Randomized", "Deterministic");
$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.
// TODO: Add binary string types and fix smalldatetime issues.
$dataTypes = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
//'binary',
//'varbinary',
//'varbinary(max)',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
//'smalldatetime',
);
// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames = array();
$colNamesAE = array();
foreach ($dataTypes as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames[$type] = "c_".$column;
$colNamesAE[$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.000000001,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:30','1985-03-31 12:40:40','2025-07-23 05:00:32','1999-12-31 00:00:00',
'1956-02-27 23:59:59','2018-09-01 14:35:30','2079-06-06 23:59:29.997','1931-10-04 19:52:21');
// 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(0x3AD2BBC2, 720, 0xEED3A109F8F7745C, 0xD6C3E0E11A25F,0x4EACCEF38788F9,0xFFFFFFFFFFFFFFFF,6230974598,0x44E4A);
$testValues['varbinary'] = array(58342,0xF3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC888,0xE4300FF,0x000005ED309D3A45,0x06CADE379,804,0x00000000000000,0xD7209FFE4);
$testValues['varbinary(max)'] = array(0xEF409CB33408, 0xD3EA762C78F,0,0xFFFFFFFFFFFFFFFFFFFFFFFFF,
0x582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1F,
0x38054,9230094389109,0xDDD4D88C4B80089D2E4A,0x88F8A8);
// 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' => 0x44E4A,
'varbinary' => 0xE4300FF,
'varbinary(max)' => 0xD3EA762C78F,
'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:30',
);
// String patterns to test with LIKE
$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('0x44E4A'),
'varbinary' => array('0xE4300FF'),
'varbinary(max)' => array('0xD3EA762C78F'),
'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'),
);
?>

View file

@ -53,4 +53,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo
$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

@ -7,6 +7,7 @@ Validates that a prepared statement can be successfully executed more than once.
PHPT_EXEC=true
--SKIPIF--
<?php
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -7,6 +7,7 @@ retrieving fields from a table including rows with all supported SQL types (28 t
PHPT_EXEC=true
--SKIPIF--
<?php
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -4,6 +4,7 @@ Fetch Field Data Test verifies the data retrieved via sqlsrv_get_field
PHPT_EXEC=true
--SKIPIF--
<?
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -7,6 +7,7 @@ by checking all fetch type modes.
PHPT_EXEC=true
--SKIPIF--
<?
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -6,6 +6,7 @@ Verifies data retrieval via "sqlsrv_fetch_object".
PHPT_EXEC=true
--SKIPIF--
<?php
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -6,6 +6,7 @@ Verifies the functionality of "sqlsrv_next_result"
PHPT_EXEC=true
--SKIPIF--
<?
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -6,6 +6,7 @@ Verifies data retrieval with scrollable result sets.
PHPT_EXEC=true
--SKIPIF--
<?php
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

View file

@ -6,7 +6,9 @@ can be successfully retrieved as streams.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?// locale must be set before 1st connection
<?
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');
?>

View file

@ -6,6 +6,7 @@ Verifies the streaming behavior with scrollable resultsets.
PHPT_EXEC=true
--SKIPIF--
<?
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif_versions_old.inc');

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"=>$userName, "PWD"=>$userPassword, "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,518 @@
<?php
// Connect and clear the procedure cache
function connect($server, $attestation_info)
{
include("MsSetup.inc");
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'CharacterSet'=>'UTF-8',
'ColumnEncryption'=>$attestation_info,
);
if ($keystore == 'akv') {
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$security_info = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVPrincipalName,
'KeyStoreSecret'=>$AKVPassword,
);
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$security_info = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVClientID,
'KeyStoreSecret'=>$AKVSecret,
);
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options = array_merge($options, $security_info);
}
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
echo "Connection failed\n";
print_r(sqlsrv_errors());
}
// 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);
$info = sqlsrv_fetch_array($stmt);
if ($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)
{
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
// two copies of each value for the two columns for each data type
foreach ($dataTypes as $type) {
$insertValues[] = $testValues[$type][$v];
$insertValues[] = $testValues[$type][$v];
}
// Insert the data using sqlsrv_prepare()
$stmt = sqlsrv_prepare($conn, $insertQuery, $insertValues);
if ($stmt == false) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at prepare\n");
}
if (sqlsrv_execute($stmt) == false) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at execute\n");
}
}
}
// 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, $comparison='', $type='')
{
if (!sqlsrv_execute($nonAEstmt)) {
print_r(sqlsrv_errors());
die("Executing non-AE statement failed!\n");
}
if(!sqlsrv_execute($AEstmt)) {
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption!\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'));
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption!\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'));
}
} 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!\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!\n");
}
} else {
print_r(sqlsrv_errors());
die("Unexpected error occurred in compareResults!\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') {
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: Thable 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'
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation)
{
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 = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null;
$param = array(array($thresholds[$type], 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, $comparison, $type);
}
}
}
// 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
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation)
{
// TODO: Pattern matching doesn't work in AE for non-string types
// without an explicit cast
foreach ($dataTypes as $type) {
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;
$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, $pattern, $type);
}
}
}
}
// 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, ["binary", "varbinary", "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 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":
case "varbinary":
case "varbinary(max)":
// Using a binary type here produces a 'Restricted data type attribute violation'
return SQLSRV_SQLTYPE_BIGINT;
default:
die("Case is missing for $type type in getSQLType.\n");
}
}
?>

View file

@ -47,6 +47,30 @@ $dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nva
$tableName = "akv_comparison_table";
// First determine if the server is AE v2 enabled
$isEnclaveEnabled = false;
$connectionOptions = array("CharacterSet"=>"UTF-8",
"database"=>$databaseName,
"uid"=>$uid,
"pwd"=>$pwd,
"ConnectionPooling"=>0);
$conn = sqlsrv_connect($server, $connectionOptions);
if (!$conn) {
fatalError("Initial connection failed\n");
} else {
$query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';";
$stmt = sqlsrv_query($conn, $query);
$info = sqlsrv_fetch_array($stmt);
if ($info['value'] == 1 and $info['value_in_use'] == 1) {
$isEnclaveEnabled = true;
}
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
}
unset($conn);
// Test every combination of the keywords above.
// Leave out good credentials to ensure that caching does not influence the
// results. The cache timeout can only be changed with SQLSetConnectAttr, so
@ -96,7 +120,8 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
array('IMSSP','-110'),
array('IMSSP','-111'),
array('IMSSP','-112'),
array('IMSSP','-113')
array('IMSSP','-113'),
array('CE400','0')
);
} else {
$columns = array();
@ -148,8 +173,11 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
sqlsrv_free_stmt($stmt);
} else {
// The INSERT query succeeded with bad credentials, which
// should only happen when encryption is not enabled.
if (AE\isDataEncrypted()) {
// should only happen when 1. encryption is not enabled or
// 2. when ColumnEncryption is set to something other than
// enabled or disabled (i.e. $i == 2), and the server is
// not enclave-enabled
if (!(!AE\isDataEncrypted() or ($i == 2 and !$isEnclaveEnabled))) {
fatalError("Successful insertion with bad credentials\n");
}
}

View file

@ -15,7 +15,6 @@ function formulateSetupQuery($tableName, &$dataTypes, &$columns, &$insertQuery)
{
$columns = array();
$queryTypes = "(";
$queryTypesAE = "(";
$valuesString = "VALUES (";
$numTypes = sizeof($dataTypes);

View file

@ -0,0 +1,113 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to 'enabled', which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $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. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with ColumnEncryption set to 'enabled'.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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 ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create 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(editErrors(sqlsrv_errors()));
die("Table creation failed!");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with ColumnEncryption set to 'enabled'
$newAttestation = 'enabled';
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'enabled');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'enabled');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
$splitDataTypes = array_chunk($dataTypes, 5);
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33546'));
$encryptionFailed = true;
continue;
}
continue;
} else {
die("Encrypting should have failed with key $targetKey and encryption type $encryptionType!\n");
}
}
if ($encryptionFailed) {
continue;
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,138 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $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 cycles through $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. Re-encrypt the table using new key and/or encryption type.
7. Compare computations as in 4. above.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// 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 an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
// 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);
$stmt = sqlsrv_query($conn, $alterQuery);
$encryptionFailed = false;
if(!$stmt) {
if (!isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r(sqlsrv_errors());
die("Encrypting failed when it shouldn't have!\n");
}
} else {
if (!isEnclaveEnabled($key)) {
die("Encrypting should have failed with key $key and encryption type $encryptionType\n");
}
}
}
}
if ($encryptionFailed) continue;
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// Try re-encrypting the table
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r(sqlsrv_errors());
die("Encrypting failed when it shouldn't have!\n");
}
} else {
if (!isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
}
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,71 @@
--TEST--
Test various settings for the ColumnEncryption keyword.
--DESCRIPTION--
For AE v2, the Column Encryption keyword must be set to [protocol]/[attestation URL].
If [protocol] is wrong, connection should fail; if the URL is wrong, connection
should succeed. This test sets ColumnEncryption to three values:
1. Random nonsense, which is interpreted as an incorrect protocol
so connection should fail.
2. Incorrect protocol with a correct attestation URL, connection should fail.
3. Correct protocol and incorrect URL, connection should succeed.
--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");
// Test with random nonsense. Connection should fail.
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'ColumnEncryption'=>"xyz",
);
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
$e = sqlsrv_errors();
checkErrors($e, array('CE400', '0'));
} else {
die("Connecting with nonsense should have failed!\n");
}
// Test with incorrect protocol and good attestation URL. Connection should fail.
// Insert a rogue 'x' into the protocol part of the attestation.
$comma = strpos($attestation, ',');
$badProtocol = substr_replace($attestation, 'x', $comma, 0);
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'ColumnEncryption'=>$badProtocol,
);
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
$e = sqlsrv_errors();
checkErrors($e, array('CE400', '0'));
} else {
die("Connecting with a bad attestation protocol should have failed!\n");
}
// Test with good protocol and incorrect attestation URL. Connection should succeed
// because the URL is only checked when an enclave computation is attempted.
$badURL = substr_replace($attestation, 'x', $comma+1, 0);
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'ColumnEncryption'=>$badURL,
);
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
print_r(sqlsrv_errors());
die("Connecting with a bad attestation URL should have succeeded!\n");
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,110 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $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. Re-encrypt the table using new key and/or encryption type.
6. Compare computations as in 4. above.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
// Create 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!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// 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);
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r(sqlsrv_errors());
die("Encrypting failed when it shouldn't have! key = $targetKey and type = $targetType\n");
}
continue;
} else {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $encryptionType\n");
}
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,93 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to the wrong attestation URL, which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $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. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with a faulty attestation URL.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--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");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
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 ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create 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!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with a faulty attestation URL
$comma = strpos($attestation, ',');
$newAttestation = substr_replace($attestation, 'x', $comma+1, 0);
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'wrongurl');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'wrongurl');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('CE405', '0'));
}
} else {
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,120 @@
--TEST--
GitHub issue 1027 - timeout option
--DESCRIPTION--
This test is a variant of the corresponding PDO test, and it verifies that setting the query timeout option should affect sqlsrv_query or sqlsrv_prepare correctly.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
const _DELAY = 5;
$message = 'Invalid value timeout specified for option SQLSRV_QUERY_TIMEOUT.';
$delay = _DELAY;
$query = "WAITFOR DELAY '00:00:$delay'; SELECT 1";
$expired = '*Query timeout expired';
function testTimeout($conn, $timeout, $prepare = false)
{
global $message;
$error = str_replace('timeout', $timeout, $message);
$options = array('QueryTimeout' => $timeout);
$sql = 'SELECT 1';
if ($prepare) {
$stmt = sqlsrv_prepare($conn, $sql, null, $options);
} else {
$stmt = sqlsrv_query($conn, $sql, null, $options);
}
if ($stmt !== false) {
echo "Expect this to fail with timeout option $timeout\n";
}
if (sqlsrv_errors()[0]['message'] !== $error) {
print_r(sqlsrv_errors());
}
}
function testErrors($conn)
{
testTimeout($conn, 1.8);
testTimeout($conn, 'xyz');
testTimeout($conn, -99, true);
testTimeout($conn, 'abc', true);
}
function checkTimeElapsed($message, $t0, $t1, $expectedDelay)
{
$elapsed = $t1 - $t0;
$diff = abs($elapsed - $expectedDelay);
$leeway = 1.0;
$missed = ($diff > $leeway);
trace("$message $elapsed secs elapsed\n");
if ($missed) {
echo $message;
echo "Expected $expectedDelay but $elapsed secs elapsed\n";
}
}
function statementTest($conn, $timeout, $prepare)
{
global $query, $expired;
$options = array('QueryTimeout' => $timeout);
$stmt = null;
$result = null;
// if timeout is 0 it means no timeout
$delay = ($timeout > 0) ? $timeout : _DELAY;
$t0 = microtime(true);
if ($prepare) {
$stmt = sqlsrv_prepare($conn, $query, null, $options);
$result = sqlsrv_execute($stmt);
} else {
$stmt = sqlsrv_query($conn, $query, null, $options);
}
$t1 = microtime(true);
if ($timeout > 0) {
if ($prepare && $result !== false) {
echo "Prepared statement should fail with timeout $timeout\n";
} elseif (!$prepare && $stmt !== false) {
echo "Query should fail with timeout $timeout\n";
} else {
// check error messages
$errors = sqlsrv_errors();
if (!fnmatch($expired, $errors[0]['message'])) {
echo "Unexpected error returned ($timeout, $prepare):\n";
print_r(sqlsrv_errors());
}
}
}
checkTimeElapsed("statementTest ($timeout, $prepare): ", $t0, $t1, $delay);
}
$conn = AE\connect();
testErrors($conn);
$rand = rand(1, 100);
$timeout = $rand % 3;
for ($i = 0; $i < 2; $i++) {
statementTest($conn, $timeout, $i);
}
sqlsrv_close($conn);
echo "Done\n";
?>
--EXPECT--
Done

View file

@ -0,0 +1,96 @@
--TEST--
GitHub issue #569 - sqlsrv_query on varchar max fields results in function sequence error
--DESCRIPTION--
This is similar to srv_569_query_varcharmax.phpt but is not limited to testing the Always Encrypted feature in Windows only.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
$conn = AE\connect(array('CharacterSet'=>'UTF-8'));
$tableName = 'srvTestTable_569_ae';
$columns = array(new AE\ColumnMeta('int', 'id', 'identity'),
new AE\ColumnMeta('nvarchar(max)', 'c1'));
AE\createTable($conn, $tableName, $columns);
$input = array();
$input[0] = 'some very large string';
$input[1] = '1234567890.1234';
$input[2] = 'über über';
$numRows = 3;
$isql = "INSERT INTO $tableName (c1) VALUES (?)";
for ($i = 0; $i < $numRows; $i++) {
$stmt = sqlsrv_prepare($conn, $isql, array($input[$i]));
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("Failed to insert row $i into $tableName");
}
}
// Select all from test table
$tsql = "SELECT id, c1 FROM $tableName ORDER BY id";
$stmt = sqlsrv_prepare($conn, $tsql);
if (!$stmt) {
fatalError("Failed to read from $tableName");
}
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("Failed to select data from $tableName");
}
// Fetch each row as an array
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$i = $row['id'] - 1;
if ($row['c1'] !== $input[$i]) {
echo "Expected $input[$i] but got: ";
var_dump($fieldVal);
}
}
// Fetch again, one field each time
sqlsrv_execute($stmt);
$i = 0;
while ($i < $numRows) {
sqlsrv_fetch($stmt);
switch ($i) {
case 0:
$fieldVal = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
break;
case 1:
$stream = sqlsrv_get_field($stmt, 1);
while (!feof( $stream)) {
$fieldVal = fread($stream, 50);
}
break;
default:
$fieldVal = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING('utf-8'));
break;
}
if ($fieldVal !== $input[$i]) {
echo 'Expected $input[$i] but got: ';
var_dump($fieldVal);
}
$i++;
}
dropTable($conn, $tableName);
echo "Done\n";
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECT--
Done

View file

@ -1,8 +1,8 @@
--TEST--
Test the existence of Windows Always Encrypted keys generated in the database setup
--DESCRIPTION--
This test iterates through the rows of sys.column_master_keys and/or
sys.column_encryption_keys to look for the specific column master key and
This test iterates through the rows of sys.column_master_keys and/or
sys.column_encryption_keys to look for the specific column master key and
column encryption key generated in the database setup
--SKIPIF--
<?php require('skipif_unix.inc'); ?>
@ -44,8 +44,8 @@ if (AE\IsQualified($conn)) {
sqlsrv_free_stmt($stmt);
}
echo "Test Successfully done.\n";
echo "Test successfully done.\n";
sqlsrv_close($conn);
?>
--EXPECT--
Test Successfully done.
Test successfully done.

View file

@ -2,6 +2,7 @@
streaming large amounts of data into a database and getting it out as a string exactly the same.
--SKIPIF--
<?
require_once('MsCommon.inc');
// locale must be set before 1st connection
setUSAnsiLocale();
require('skipif.inc');