Merge pull request #787 from Microsoft/dev

Dev
This commit is contained in:
Jenny Tam 2018-05-30 12:10:13 -07:00 committed by GitHub
commit 8f9ecdebce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 4128 additions and 1374 deletions

View file

@ -3,7 +3,41 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## Windows/Linux/macOS 5.2.0 - 2017-02-14
## 5.2.1-preview - 2018-06-01
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is also available to Linux or macOS users
- Added support for macOS High Sierra (requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017))
### Fixed
- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connection
- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling bug
- Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameter failed when the query in the stored procedure returned no data. The test case has been added to the test lab.
- Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - AE - Retrieving a negative decimal value (edge case) as output parameter causes truncation
- Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - AE - Cannot insert double with precision and scale (38, 38)
- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - AE - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers
- Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO lastInsertId such that data types other than integers can be supported
- Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY
- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC
### Limitations
- No support for inout / output params when using sql_variant type
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connection will not work
- Always Encrypted feature, which requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)
- only Windows Certificate Store and Azure Key Vault are supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted feature enabled, Named Parameters in Sub Queries are not supported
- [Always Encrypted limitations](https://docs.microsoft.com/en-us/sql/connect/php/using-always-encrypted-php-drivers?view=sql-server-2017#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Connection pooling on Linux or macOS not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.6
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostics 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)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
## Windows/Linux/macOS 5.2.0 - 2018-03-23
Updated PECL release packages. Here is the list of updates:
### Added

View file

@ -45,7 +45,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt
ENV PATH="/opt/mssql-tools/bin:${PATH}"
#install coveralls
RUN pip install --upgrade pip && pip install cpp-coveralls
RUN python -m pip install --upgrade pip && pip install cpp-coveralls
#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html )
#One option is to get source from zip file of repository.

View file

@ -9,12 +9,12 @@ These instruction install PHP 7.2 by default -- see the notes at the beginning o
- [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7)
- [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9)
- [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12)
- [Installing the drivers on macOS El Capitan and Sierra](#installing-the-drivers-on-macos-el-capitan-and-sierra)
- [Installing the drivers on macOS El Capitan, Sierra and High Sierra](#installing-the-drivers-on-macos-el-capitan-sierra-and-high-sierra)
## Installing the drivers on Ubuntu 16.04 and 17.10
> [!NOTE]
> To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands.
> [!NOTE]
> To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands.
### Step 1. Install PHP
```
@ -28,12 +28,12 @@ Install the ODBC driver for Ubuntu by following the instructions on the [Linux a
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
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
exit
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
### Step 4. Install Apache and configure driver loading
```
@ -42,8 +42,8 @@ apt-get install libapache2-mod-php7.2 apache2
a2dismod mpm_event
a2enmod mpm_prefork
a2enmod php7.2
echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/php.ini
echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/php.ini
echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini
echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini
```
### Step 5. Restart Apache and test the sample script
```
@ -53,8 +53,8 @@ To test your installation, see [Testing your installation](#testing-your-install
## Installing the drivers on Red Hat 7
> [!NOTE]
> To install PHP 7.0 or 7.1, replace remi-php72 with remi-php70 or remi-php71 respectively in the following commands.
> [!NOTE]
> To install PHP 7.0 or 7.1, replace remi-php72 with remi-php70 or remi-php71 respectively in the following commands.
### Step 1. Install PHP
@ -79,12 +79,12 @@ 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 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
exit
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
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:
```
@ -116,8 +116,8 @@ To test your installation, see [Testing your installation](#testing-your-install
## Installing the drivers on Debian 8 and 9
> [!NOTE]
> To install PHP 7.0 or 7.1, replace 7.2 in the following commands with 7.0 or 7.1.
> [!NOTE]
> To install PHP 7.0 or 7.1, replace 7.2 in the following commands with 7.0 or 7.1.
### Step 1. Install PHP
```
@ -126,7 +126,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.2 php7.2-dev php7.2-xml
apt-get install -y php7.2 php7.2-dev php7.2-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/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
@ -140,12 +140,12 @@ locale-gen
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
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
exit
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
### Step 4. Install Apache and configure driver loading
```
@ -154,8 +154,8 @@ apt-get install libapache2-mod-php7.2 apache2
a2dismod mpm_event
a2enmod mpm_prefork
a2enmod php7.2
echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/php.ini
echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/php.ini
echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini
echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini
```
### Step 5. Restart Apache and test the sample script
```
@ -165,10 +165,10 @@ To test your installation, see [Testing your installation](#testing-your-install
## Installing the drivers on Suse 12
> [!NOTE]
> To install PHP 7.0, skip the command below adding the repository - 7.0 is the default PHP on suse 12.
> To install PHP 7.1, replace the repository URL below with the following URL:
`http://download.opensuse.org/repositories/devel:/languages:/php:/php71/SLE_12/devel:languages:php:php71.repo`
> [!NOTE]
> To install PHP 7.0, skip the command below adding the repository - 7.0 is the default PHP on suse 12.
> To install PHP 7.1, replace the repository URL below with the following URL:
`http://download.opensuse.org/repositories/devel:/languages:/php:/php71/SLE_12/devel:languages:php:php71.repo`
### Step 1. Install PHP
```
@ -182,12 +182,12 @@ Install the ODBC driver for Suse 12 by following the instructions on the [Linux
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
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
exit
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
### Step 4. Install Apache and configure driver loading
```
@ -203,28 +203,30 @@ 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 El Capitan and Sierra
## Installing the drivers on macOS El Capitan, Sierra and High Sierra
If you do not already have it, install brew as follows:
```
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
> [!NOTE]
> To install PHP 7.0 or 7.1, replace php72 with php70 or php71 respectively in the following commands.
> [!NOTE]
> To install PHP 7.0 or 7.1, replace php@7.2 with php@7.0 or php@7.1 respectively in the following commands.
### Step 1. Install PHP
```
brew tap
brew tap homebrew/dupes
brew tap homebrew/versions
brew tap homebrew/homebrew-php
brew install php72 --with-pear --with-httpd24 --with-cgi
echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
brew tap homebrew/core
brew install php@7.2
```
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.2
```
### Step 2. Install prerequisites
Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
@ -235,13 +237,20 @@ brew install autoconf automake libtool
### Step 3. Install the PHP drivers for Microsoft SQL Server
```
chmod -R ug+w /usr/local/opt/php72/lib/php
pear config-set php_ini /usr/local/etc/php/7.2/php.ini system
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
### Step 4. Install Apache and configure driver loading
```
brew install apache2
```
To find the Apache configuration file for your Apache installation, run
```
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.2/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,12 +2,9 @@
**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 to handle the low-level communication with SQL Server.
This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.* with improvements on both drivers and some limitations (see Limitations below for details). Upcoming releases will contain additional functionality, bug fixes, and more (see Plans below for more details).
SQL Server Team
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/en-us/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.
This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.* with improvements on both drivers and some limitations (see Limitations below for details). Upcoming releases will contain additional functionalities, bug fixes, and more.
## Take our survey
@ -45,14 +42,14 @@ Thank you for taking the time to participate in our last survey. You can continu
## Prerequisites
For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/en-us/sql/connect/php/system-requirements-for-the-php-sql-driver) on MSDN.
For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/en-us/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs.
On the client machine:
- PHP 7.0.x, 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows)
- A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP
- [Microsoft ODBC Driver 11][odbc11], [Microsoft ODBC Driver 13][odbc13], or [Microsoft ODBC Driver 17][odbc17]
- [Microsoft ODBC Driver 17][odbc17], [Microsoft ODBC Driver 13][odbc13], or [Microsoft ODBC Driver 11][odbc11]
On the server side, Microsoft SQL Server 2008 R2 and above on Windows is supported, as is Microsoft SQL Server 2016 and above on Linux.
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
@ -60,16 +57,16 @@ The drivers are distributed as pre-compiled extensions for PHP found on the [rel
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.
To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/en-us/sql/connect/php/loading-the-php-sql-driver) on MSDN.
To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/en-us/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs.
Finally, restart the Web server.
## Install (UNIX)
For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on MSDN](https://docs.microsoft.com/en-us/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](https://docs.microsoft.com/en-us/sql/connect/php/installation-tutorial-linux-mac).
## Sample Code
For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on MSDN](https://docs.microsoft.com/en-us/sql/connect/php/code-samples-for-php-sql-driver).
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/en-us/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.
@ -96,15 +93,9 @@ The version number may have trailing pre-release version identifiers to indicate
## Guidelines for Reporting Issues
We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you:
- First check the [FAQ](https://github.com/Microsoft/msphpsql/wiki/FAQ)
- Report each issue as a new issue (but check first if it's already been reported)
- Try to be detailed in your report. Useful information for good bug reports includes:
* What you are seeing and what the expected behaviour is
* Can you connect to SQL Server via `sqlcmd`?
* Which driver: SQLSRV or PDO_SQLSRV?
* Environment details: e.g. PHP version, thread safe (TS) or non-thread safe (NTS), 32-bit or 64-bit?
* Table schema (for some issues, the data types make a big difference!)
* Any other relevant information you want to share
- Try to include a PHP script demonstrating the isolated problem.
- Please address the questions in the new issue template and provide scripts, table schema, and/or any details that may help reproduce the problem(s)
Thank you!
@ -121,8 +112,6 @@ Thank you!
**A:** Yes. Please submit pull requests to the **dev** branch and not the **master** branch.
## License
The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details.
@ -133,7 +122,7 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf
## Resources
**Documentation**: [MSDN Online Documentation][phpdoc].
**Documentation**: [Microsoft Docs Online][phpdoc].
**Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog].
@ -155,16 +144,6 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf
[odbc13]: https://www.microsoft.com/download/details.aspx?id=50420
[odbc17]: https://github.com/Microsoft/msphpsql/tree/master/ODBC%2017%20binaries%20preview
[odbcLinux]: https://msdn.microsoft.com/library/hh568454(v=sql.110).aspx
[odbc17]: https://www.microsoft.com/download/details.aspx?id=56567
[PHPMan]: http://php.net/manual/install.unix.php
[LinuxDM]: https://msdn.microsoft.com/library/hh568449(v=sql.110).aspx
[httpd_source]: http://httpd.apache.org/
[apr_source]: http://apr.apache.org/
[httpdconf]: http://php.net/manual/en/install.unix.apache2.php

View file

@ -15,46 +15,39 @@ environment:
TEST_PHP_SQL_UID: sa
SQLSRV_DBNAME: msphpsql_sqlsrv
PDOSQLSRV_DBNAME: msphpsql_pdosqlsrv
PHP_DEPSVER: 7.0
PHP_SDK: c:\projects\php
PYTHON: c:\Python36
# For details about Appveyor build worker images (VM template): https://www.appveyor.com/docs/build-environment/#build-worker-images
matrix:
- BUILD_PLATFORM: x64
TEST_PHP_SQL_SERVER: (local)\SQL2012SP1
SQL_INSTANCE: SQL2012SP1
PHP_VC: 14
PHP_MAJOR_VER: 7.1
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
BUILD_PLATFORM: x64
TEST_PHP_SQL_SERVER: (local)\SQL2017
SQL_INSTANCE: SQL2017
PHP_VC: 15
PHP_MAJOR_VER: 7.2
PHP_MINOR_VER: latest
PHP_SDK_DIR: c:\projects\php\x64
PHP_INSTALL_DIR: c:\projects\php\x64\bin
PHP_EXE_PATH: x64\Release_TS
THREAD: ts
platform: x64
- BUILD_PLATFORM: x86
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
BUILD_PLATFORM: x86
TEST_PHP_SQL_SERVER: (local)\SQL2016
SQL_INSTANCE: SQL2016
PHP_VC: 14
PHP_MAJOR_VER: 7.1
PHP_MINOR_VER: latest
PHP_SDK_DIR: c:\projects\php\x86
PHP_INSTALL_DIR: c:\projects\php\x86\bin
PHP_ZTS: --disable-zts
PHP_EXE_PATH: Release
THREAD: nts
platform: x86
# PHP_MAJOR_VER is PHP major version to build (7.0, 7.1)
# PHP_MAJOR_VER is PHP major version to build (7.2, 7.1)
# PHP_MINOR_VER is PHP point release number (or latest for latest release)
# PHP_INSTALL_DIR is where the built PHP binaries go
# PHP_SDK_DIR is where PHP source is extracted to (e.g. PHP_SDK_DIR\php-7.0.14-src)
# PHP_SDK is where PHP sdk binary tools are extracted to
# PHP_VC is the Visual C++ version
# PHP_ZTS is defined to disable thread safe build
# Build worker image (VM template)
image: Visual Studio 2015
# PHP_EXE_PATH is the relative path from php src folder to php executable
# THREAD is either non-thread-safe (nts) or thread-safe (ts)
matrix:
fast_finish: true
#services:
#- mssql2012sp1
# clone directory (or %APPVEYOR_BUILD_FOLDER%)
clone_folder: c:\projects\sqlphp
@ -65,8 +58,8 @@ install:
- echo start SQL Server
# Based on http://www.appveyor.com/docs/services-databases
- ps: >-
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null ;
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null ;
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null;
$instanceName = $env:SQL_INSTANCE;
$uri = "ManagedComputer[@Name='$env:COMPUTERNAME']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']";
@ -79,10 +72,6 @@ install:
Set-Service SQLBrowser -StartupType Manual;
Start-Service SQLBrowser;
- echo Downloading prerequisites
- ps: |
$client = New-Object Net.WebClient;
$client.Headers.Add("user-agent", "appveyor-ci-build1");
$client.DownloadFile("http://windows.php.net/downloads/php-sdk/php-sdk-binary-tools-20110915.zip", "c:\projects\php-sdk-binary-tools-20110915.zip");
- ps: |
$client = New-Object Net.WebClient;
$client.Headers.Add("user-agent", "appveyor-ci-build2");
@ -92,91 +81,73 @@ install:
} Else {
$env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER;
}
- ps: |
$client = New-Object Net.WebClient;
$client.Headers.Add("user-agent", "appveyor-ci-build3");
$client.DownloadFile("http://windows.php.net/downloads/releases/php-" + ${env:PHP_VERSION} + "-src.zip", ${env:APPVEYOR_BUILD_FOLDER} + "\..\php.zip");
- echo Downloading MSODBCSQL 13.1
- echo Downloading MSODBCSQL 17.1
# 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/D/5/E/D5EEF288-A277-45C8-855B-8E2CB7E25B96/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql.msi" /q
- ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/msodbcsql_17.1.0.1_x64.msi', 'c:\projects\msodbcsql_17.1.0.1_x64.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.1.0.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL
- echo Checking the version of MSODBCSQL
- reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 13 for SQL Server"
- dir C:\Windows\System32\msodbcsql13.dll
- reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server"
- dir %WINDIR%\System32\msodbcsql*.dll
- cd c:\projects
- 7z x -y .\php-sdk-binary-tools-20110915.zip -o%PHP_SDK%
- 7z x -y .\php.zip -o%PHP_SDK_DIR%
- echo update SQL connection string
- ps: (Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv\MsSetup.inc) | ForEach-Object { $_ -replace "TARGET_SERVER", ${env:TEST_PHP_SQL_SERVER} -replace "TARGET_DATABASE", ${env:PDOSQLSRV_DBNAME} -replace "TARGET_USERNAME", ${env:TEST_PHP_SQL_UID} -replace "TARGET_PASSWORD", ${env:TEST_PHP_SQL_PWD} } | Set-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv\MsSetup.inc
- ps: Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv\MsSetup.inc
- ps: Select-String ${env:SQL_INSTANCE} ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv\MsSetup.inc
- ps: Select-String ${env:PDOSQLSRV_DBNAME} ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv\MsSetup.inc
- ps: (Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv\MsSetup.inc) | ForEach-Object { $_ -replace "TARGET_SERVER", ${env:TEST_PHP_SQL_SERVER} -replace "TARGET_DATABASE", ${env:SQLSRV_DBNAME} -replace "TARGET_USERNAME", ${env:TEST_PHP_SQL_UID} -replace "TARGET_PASSWORD", ${env:TEST_PHP_SQL_PWD} } | Set-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv\MsSetup.inc
- ps: Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv\MsSetup.inc
- ps: Select-String ${env:SQL_INSTANCE} ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv\MsSetup.inc
- ps: Select-String ${env:SQLSRV_DBNAME} ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv\MsSetup.inc
- echo install opencppcoverage
- choco install opencppcoverage
- set path=C:\Program Files\OpenCppCoverage;%path%
- set path=C:\Program Files\OpenCppCoverage;%PYTHON%;%PYTHON%\Scripts;%path%
build_script:
- '"C:\\Program Files (x86)\\Microsoft Visual Studio %PHP_VC%.0\\VC\\vcvarsall.bat" %BUILD_PLATFORM%'
- Echo copy msphp code to ext folder
- mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv
- mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv\shared
- mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv
- mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv\shared
- copy /Y %APPVEYOR_BUILD_FOLDER%\source\sqlsrv %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv
- copy /Y %APPVEYOR_BUILD_FOLDER%\source\shared %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv\shared
- copy /Y %APPVEYOR_BUILD_FOLDER%\source\shared %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv\shared
- copy /Y %APPVEYOR_BUILD_FOLDER%\source\pdo_sqlsrv %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv
- cd %PHP_SDK_DIR%\php-%PHP_VERSION%-src
- cd
- dir
- '%PHP_SDK%\bin\phpsdk_setvars.bat'
- buildconf.bat
# only build CLI and MSSQL extensions
- configure.bat --disable-all %PHP_ZTS% --enable-cli --enable-sqlsrv=shared --with-pdo-sqlsrv=shared --enable-pdo=shared --with-prefix=%PHP_INSTALL_DIR%
- copy php.ini-development php.ini
- echo extension_dir=%PHP_INSTALL_DIR%\ext >> php.ini
- echo extension=php_sqlsrv.dll >> php.ini
- echo extension=php_pdo_sqlsrv.dll >> php.ini
- nmake
- nmake install
- Echo copy php.ini and run-tests.php from php source to install directory.
- copy php.ini %PHP_INSTALL_DIR%
- copy run-tests.php %PHP_INSTALL_DIR%
- dir %PHP_INSTALL_DIR%
- copy %APPVEYOR_BUILD_FOLDER%\buildscripts\*.py c:\projects
- cd c:\projects
- python -V
- python builddrivers.py --PHPVER=%PHP_VERSION% --ARCH=%BUILD_PLATFORM% --THREAD=%THREAD% --SOURCE=%APPVEYOR_BUILD_FOLDER%\source --TESTING --NO_RENAME
- cd c:\projects\php-sdk\phpdev\vc%PHP_VC%\%BUILD_PLATFORM%\php-%PHP_VERSION%-src\
- set PHP_SRC_DIR=%CD%\ext
- cd %PHP_EXE_PATH%
- set PHP_EXE_PATH=%CD%
- echo Showing the last few lines of php.ini
- ps: Get-Content ${env:PHP_EXE_PATH}\php.ini -Tail 5
- php --ini
- php -m
test_script:
- cd %PHP_INSTALL_DIR%
- php --ini
- php -i
- python -V
- Echo setup test database for SQLSRV tests - %SQLSRV_DBNAME%
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %SQLSRV_DBNAME%
- Echo setup test database for PDO_SQLSRV tests - %PDOSQLSRV_DBNAME%
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %PDOSQLSRV_DBNAME%
- ps: >-
If ($env:SQL_INSTANCE -Match "SQL2016") {
If ($env:BUILD_PLATFORM -Match "x86") {
Write-Host "Running phpt tests via OpenCppCoverage..."
$ext_dir = ${env:PHP_SDK_DIR} + "\php-" + ${env:PHP_VERSION} + "-src\ext";
OpenCppCoverage.exe --sources $ext_dir\sqlsrv --sources $ext_dir\pdo_sqlsrv --modules ext\php_sqlsrv.dll --modules ext\php_pdo_sqlsrv.dll --export_type=cobertura:.\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8;
type ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log;
OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8;
Write-Host "Showing the last 25 lines of the log file..."
Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -Tail 25;
ls *.xml
} Else {
Write-Host "Running phpt tests the regular way..."
.\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv\*.phpt | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv.log -encoding UTF8;
type ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv.log;
Write-Host "Showing the last 25 lines of the log file..."
Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\sqlsrv.log -Tail 25;
.\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv\*.phpt | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv.log -encoding UTF8;
type ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv.log;
Write-Host "Showing the last 25 lines of the log file..."
Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\pdo_sqlsrv.log -Tail 25;
}
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\cleanup_dbs.py -dbname %SQLSRV_DBNAME%
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\cleanup_dbs.py -dbname %PDOSQLSRV_DBNAME%
- cd %PHP_INSTALL_DIR%
- ps: $fileExists = Test-Path "coverage.xml"
- cd %PHP_EXE_PATH%
- ps: $fileExists = Test-Path "c:\projects\coverage.xml"
- ps: >-
If ($fileExists -eq $true) {
cd c:\projects
Write-Host "Running coverage analysis...";
$env:PATH = ${env:PHP_INSTALL_DIR} + $env:PATH;
Write-Host "Showing the packages...";
Select-String package .\coverage.xml;
Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh
bash codecov.sh -f "coverage.xml"
cd ${env:PHP_EXE_PATH}
}
after_test:

View file

@ -40,8 +40,8 @@ class BuildDriver(object):
testing # whether the user has turned on testing mode
"""
def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, path, testing):
self.util = BuildUtil(phpver, driver, arch, thread, debug)
def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, path, testing, no_rename):
self.util = BuildUtil(phpver, driver, arch, thread, no_rename, debug)
self.repo = repo
self.branch = branch
self.source_path = source
@ -123,8 +123,13 @@ class BuildDriver(object):
"""
work_dir = os.path.dirname(os.path.realpath(__file__))
if self.source_path is None:
# This will download from the specified branch on GitHub repo and copy the source
get_source = False if self.source_path is None else True
if self.repo is None or self.branch is None:
# If GitHub repo or branch is None, get the source locally
get_source = True
if not get_source:
# This will download from the specified branch on GitHub repo and copy the source
self.util.download_msphpsql_source(repo, branch)
else:
source = self.source_path
@ -164,7 +169,8 @@ class BuildDriver(object):
else:
self.util.copy_binary(ext_dir, dest_drivers, self.util.driver, '.dll')
self.util.copy_binary(ext_dir, dest_symbols, self.util.driver, '.pdb')
return ext_dir
def build(self):
"""This is the main entry point of building drivers for PHP.
@ -186,15 +192,15 @@ class BuildDriver(object):
logfile = self.util.get_logfile_name()
try:
self.build_extensions(root_dir, logfile)
ext_dir = self.build_extensions(root_dir, logfile)
print('Build Completed')
except:
print('Something went wrong, launching log file', logfile)
# display log file only when not testing
if not self.testing:
os.startfile(os.path.join(root_dir, 'php-sdk', logfile))
os.chdir(work_dir)
break
os.chdir(work_dir)
exit(1)
if not self.testing:
choice = input("Rebuild using the same configuration(yes) or quit (no) [yes/no]: ")
@ -237,6 +243,7 @@ if __name__ == '__main__':
parser.add_argument('--SOURCE', default=None, help="a local path to source file (default: None)")
parser.add_argument('--TESTING', action='store_true', help="turns on testing mode (default: False)")
parser.add_argument('--DESTPATH', default=None, help="an alternative destination for the drivers (default: None)")
parser.add_argument('--NO_RENAME', action='store_true', help="drivers will not be renamed(default: False)")
args = parser.parse_args()
@ -250,6 +257,7 @@ if __name__ == '__main__':
source = args.SOURCE
path = args.DESTPATH
testing = args.TESTING
no_rename = args.NO_RENAME
if phpver is None:
# starts interactive mode, testing mode is False
@ -277,8 +285,10 @@ if __name__ == '__main__':
repo = 'Microsoft'
if branch == '':
branch = 'dev'
arch_version = arch_version.lower()
else:
repo = branch = None
arch_version = arch_version.lower()
arch = 'x64' if arch_version == 'yes' or arch_version == 'y' or arch_version == '' else 'x86'
debug_mode = debug_mode.lower()
@ -293,5 +303,6 @@ if __name__ == '__main__':
branch,
source,
path,
testing)
testing,
no_rename)
builder.build()

View file

@ -31,14 +31,16 @@ class BuildUtil(object):
driver # all, sqlsrv, or pdo_sqlsrv
arch # x64 or x86
thread # nts or ts
no_rename # do NOT rename the drivers if True
debug_enabled # whether debug is enabled
"""
def __init__(self, phpver, driver, arch, thread, debug_enabled = False):
def __init__(self, phpver, driver, arch, thread, no_rename, debug_enabled = False):
self.phpver = phpver
self.driver = driver.lower()
self.arch = arch.lower()
self.thread = thread.lower()
self.no_rename = no_rename
self.debug_enabled = debug_enabled
def major_version(self):
@ -409,8 +411,9 @@ class BuildUtil(object):
# already been modified prior to building the extensions
shutil.rmtree(os.path.join(phpSDK, 'Source'), ignore_errors=True)
# Next, rename the newly compiled PHP extensions
self.rename_binaries(sdk_dir)
# Next, rename the newly compiled PHP extensions, if required
if not self.no_rename:
self.rename_binaries(sdk_dir)
# Final step, copy the binaries to the right place
ext_dir = self.copy_binaries(sdk_dir, copy_to_ext)
@ -440,26 +443,47 @@ class BuildUtil(object):
def copy_binary(self, from_dir, dest_dir, driver, suffix):
"""Copy sqlsrv or pdo_sqlsrv binary (based on *suffix*) to *dest_dir*."""
if suffix == '.dll':
if not self.no_rename and suffix == '.dll':
binary = self.driver_new_name(driver, suffix)
else:
binary = self.driver_name(driver, suffix)
shutil.copy2(os.path.join(from_dir, binary), dest_dir)
if suffix == '.dll':
php_ini_file = os.path.join(from_dir, 'php.ini')
with open(php_ini_file, 'a') as php_ini:
php_ini.write('extension=' + binary + '\n');
def copy_binaries(self, sdk_dir, copy_to_ext):
"""Copy the sqlsrv and/or pdo_sqlsrv binaries, including the pdb files,
to the right place, depending on *copy_to_ext*. The default is to
copy them to the 'ext' folder.
"""
"""
# Get php.ini file from php.ini-production
build_dir = self.build_abs_path(sdk_dir)
php_ini_file = os.path.join(build_dir, 'php.ini')
print('Setting up php ini file', php_ini_file)
# Copy php.ini-production file to php.ini
phpsrc = self.phpsrc_root(sdk_dir)
shutil.copy(os.path.join(phpsrc, 'php.ini-production'), php_ini_file)
# Copy run-tests.php as well
phpsrc = self.phpsrc_root(sdk_dir)
shutil.copy(os.path.join(phpsrc, 'run-tests.php'), build_dir)
print('Copying the binaries from', build_dir)
if copy_to_ext:
dest_dir = os.path.join(build_dir, 'ext')
ext_dir_line = 'extension_dir=ext\\'
else:
ext_dir_line = 'extension_dir=.\\'
# Simply make a copy of the binaries in sdk_dir
dest_dir = sdk_dir
print('Destination:', dest_dir)
with open(php_ini_file, 'a') as php_ini:
php_ini.write(ext_dir_line + '\n')
# Now copy the binaries
if self.driver == 'all':

View file

@ -1,27 +1,20 @@
.+Please check the [FAQ (frequently-asked questions)](https://github.com/Microsoft/msphpsql/wiki/FAQ) first. If you have other questions or something to report, please address the following:
+## Driver version or file name
+Please tell us what the PHP driver version or file name is.
+## PHP Driver version or file name
+
+## SQL Server version
+Please tell us what the SQL Server version is.
+
+## Client operating system
+Please tell us what oprating system the client program is running on.
+
+## PHP version
+Please tell us which version of PHP you are running.
+
+## Microsoft ODBC Driver version
+Please tell us which version of the Microsoft ODBC Driver you are using.
+
+## Table schema
+Please tell us the table schema
+
+## Problem description
+Please share more details with us.
+
+## Expected behavior and actual behavior
+Please tell us what should happen and what happened instead
+
+## Repro code
+Please share repro code with us, or tell us how to reproduce the issue.
+## Repro code or steps to reproduce

View file

@ -29,7 +29,7 @@ typedef const zend_function_entry pdo_sqlsrv_function_entry;
namespace {
const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;";
const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer
const size_t LAST_INSERT_ID_BUFF_LEN = 50; // size of the buffer to hold the string value of the last inserted id, which may be an int, bigint, decimal(p,0) or numeric(p,0)
const char SEQUENCE_CURRENT_VALUE_QUERY[] = "SELECT CURRENT_VALUE FROM SYS.SEQUENCES WHERE NAME=%s";
const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( SEQUENCE_CURRENT_VALUE_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes
@ -40,17 +40,20 @@ const char Server[] = "Server";
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char ConnectionPooling[] = "ConnectionPooling";
const char Authentication[] = "Authentication";
const char Driver[] = "Driver";
#ifdef _WIN32
const char ColumnEncryption[] = "ColumnEncryption";
const char ConnectionPooling[] = "ConnectionPooling";
#ifdef _WIN32
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
#endif // _WIN32
const char Database[] = "Database";
const char Driver[] = "Driver";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
const char KeyStoreSecret[] = "KeyStoreSecret";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_Option[] = "MultipleActiveResultSets";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
@ -231,7 +234,6 @@ const connection_option PDO_CONN_OPTS[] = {
CONN_ATTR_STRING,
driver_set_func::func
},
#ifdef _WIN32
{
PDOConnOptionNames::ColumnEncryption,
sizeof(PDOConnOptionNames::ColumnEncryption),
@ -241,6 +243,7 @@ const connection_option PDO_CONN_OPTS[] = {
CONN_ATTR_STRING,
column_encryption_set_func::func
},
#ifdef _WIN32
{
PDOConnOptionNames::ConnectRetryCount,
sizeof( PDOConnOptionNames::ConnectRetryCount ),
@ -287,6 +290,33 @@ const connection_option PDO_CONN_OPTS[] = {
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::KeyStoreAuthentication,
sizeof( PDOConnOptionNames::KeyStoreAuthentication ),
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
ODBCConnOptions::KeyStoreAuthentication,
sizeof( ODBCConnOptions::KeyStoreAuthentication ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
PDOConnOptionNames::KeyStorePrincipalId,
sizeof( PDOConnOptionNames::KeyStorePrincipalId ),
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
ODBCConnOptions::KeyStorePrincipalId,
sizeof( ODBCConnOptions::KeyStorePrincipalId ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
PDOConnOptionNames::KeyStoreSecret,
sizeof( PDOConnOptionNames::KeyStoreSecret ),
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
ODBCConnOptions::KeyStoreSecret,
sizeof( ODBCConnOptions::KeyStoreSecret ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
PDOConnOptionNames::LoginTimeout,
sizeof( PDOConnOptionNames::LoginTimeout ),
@ -362,7 +392,7 @@ const connection_option PDO_CONN_OPTS[] = {
{
PDOConnOptionNames::TransparentNetworkIPResolution,
sizeof(PDOConnOptionNames::TransparentNetworkIPResolution),
SQLSRV_CONN_OPTION_TRANSPARANT_NETWORK_IP_RESOLUTION,
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
ODBCConnOptions::TransparentNetworkIPResolution,
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
CONN_ATTR_STRING,
@ -1253,7 +1283,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
else {
char* quoted_table = NULL;
size_t quoted_len = 0;
int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), &quoted_table, &quoted_len, PDO_PARAM_NULL TSRMLS_CC );
int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strnlen_s( name ), &quoted_table, &quoted_len, PDO_PARAM_NULL TSRMLS_CC );
SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name.");
snprintf( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, quoted_table );
sqlsrv_free( quoted_table );
@ -1270,7 +1300,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len;
wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast<const char*>( last_insert_id_query ), static_cast<unsigned int>( strlen( last_insert_id_query )), &wsql_len );
wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast<const char*>( last_insert_id_query ), static_cast<unsigned int>( strnlen_s( last_insert_id_query )), &wsql_len );
CHECK_CUSTOM_ERROR( wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) {
throw core::CoreException();

View file

@ -189,7 +189,7 @@ void conn_string_parser::add_key_value_pair( _In_reads_(len) const char* value,
if( !valid ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, this->current_key_name );
}
string_parser::add_key_value_pair( value, len );
}

View file

@ -1180,9 +1180,10 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
}
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
int pdo_type = param->param_type;
// 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 & ~PDO_PARAM_INPUT_OUTPUT ) {
switch (pdo_type) {
case PDO_PARAM_BOOL:
case PDO_PARAM_INT:
php_out_type = SQLSRV_PHPTYPE_INT;

View file

@ -406,8 +406,28 @@ pdo_error PDO_ERRORS[] = {
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, false }
},
{
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -84, false }
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*) "Error converting a double (value out of range) to an integer.", -84, false }
},
{
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the KeyStoreAuthentication keyword. Only KeyVaultPassword or KeyVaultClientSecret is allowed.", -85, false }
},
{
SQLSRV_ERROR_AKV_AUTH_MISSING,
{ IMSSP, (SQLCHAR*) "The authentication method for Azure Key Vault is missing. KeyStoreAuthentication must be set to KeyVaultPassword or KeyVaultClientSecret.", -86, false }
},
{
SQLSRV_ERROR_AKV_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The username or client Id for Azure Key Vault is missing.", -87, false }
},
{
SQLSRV_ERROR_AKV_SECRET_MISSING,
{ IMSSP, (SQLCHAR*) "The password or client secret for Azure Key Vault is missing.", -88, false }
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -89, false}
},
{ UINT_MAX, {} }
};
@ -473,7 +493,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned
SQLSRV_ASSERT( err == true, "No ODBC error was found" );
}
SQLSRV_ASSERT(strlen(reinterpret_cast<const char*>(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow");
SQLSRV_ASSERT(strnlen_s(reinterpret_cast<const char*>(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow");
strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast<const char*>(error->sqlstate));
switch( dbh->error_mode ) {
@ -486,7 +506,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned
break;
case PDO_ERRMODE_WARNING:
if( !warning ) {
size_t msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
size_t msg_len = strnlen_s( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
+ MAX_DIGITS + WARNING_MIN_LENGTH + 1;
sqlsrv_malloc_auto_ptr<char> msg;
msg = static_cast<char*>( sqlsrv_malloc( msg_len ) );
@ -525,7 +545,7 @@ bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigne
SQLSRV_ASSERT( err == true, "No ODBC error was found" );
}
SQLSRV_ASSERT( strlen( reinterpret_cast<const char*>( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow");
SQLSRV_ASSERT( strnlen_s( reinterpret_cast<const char*>( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow");
strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast<const char*>( error->sqlstate ));
switch( pdo_stmt->dbh->error_mode ) {
@ -612,7 +632,7 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error TSRMLS_DC )
SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" );
sqlsrv_malloc_auto_ptr<char> ex_msg;
size_t ex_msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE +
size_t ex_msg_len = strnlen_s( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE +
12 + 1; // 12 = "SQLSTATE[]: "
ex_msg = reinterpret_cast<char*>( sqlsrv_malloc( ex_msg_len ));
snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message );

View file

@ -709,7 +709,7 @@ int FormattedPrintA( IFormattedPrintOutput<char> * output, const char *format, v
++text.sz;
}
textlen = (int)strlen(text.sz); /* compute length of text */
textlen = (int)strnlen_s(text.sz); /* compute length of text */
}
break;

View file

@ -138,6 +138,12 @@ int mplat_strcat_s( char * dest, size_t destSize, const char * src )
}
return 0;
}
size_t strnlen_s(const char * _Str, size_t _MaxCount)
{
return (_Str==0) ? 0 : strnlen(_Str, _MaxCount);
}
//
// End copy functions
//----------------------------------------------------------------------------

View file

@ -31,6 +31,8 @@ int mplat_memcpy_s(void *_S1, size_t _N1, const void *_S2, size_t _N);
int mplat_strcat_s( char *strDestination, size_t numberOfElements, const char *strSource );
int mplat_strcpy_s(char * _Dst, size_t _SizeInBytes, const char * _Src);
size_t strnlen_s(const char * _Str, size_t _MaxCount = INT_MAX);
// Copy
#define memcpy_s mplat_memcpy_s
#define strcat_s mplat_strcat_s

View file

@ -7,13 +7,13 @@
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
@ -45,6 +45,9 @@ const int DEFAULT_CONN_STR_LEN = 2048;
// length of buffer used to retrieve information for client and server info buffers
const int INFO_BUFFER_LEN = 256;
// length for name of keystore used in CEKeyStoreData
const int MAX_CE_NAME_LEN = 260;
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
@ -63,14 +66,17 @@ const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};";
// *** internal function prototypes ***
void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[],
void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[],
void* driver,_Inout_ std::string& connection_string TSRMLS_DC );
void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
const char* get_processor_arch( void );
void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC );
connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len TSRMLS_DC );
void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC );
void load_azure_key_vault( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const DWORD config_value, size_t key_size);
void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const char* config_value, size_t key_size);
}
// core_sqlsrv_connect
@ -79,7 +85,7 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l
// henv_cp - connection pooled env context
// henv_ncp - non connection pooled env context
// server - name of the server we're connecting to
// uid - username
// uid - username
// pwd - password
// options_ht - zend_hash list of options
// err - error callback to put into the connection's context
@ -89,8 +95,8 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l
// A sqlsrv_conn structure. An exception is thrown if an error occurs
sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory,
_Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[],
_Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[],
_In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC )
{
@ -106,13 +112,13 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
#else
sqlsrv_context* henv = &henv_ncp; // by default do not use the connection pooling henv
is_pooled = false;
#endif // _WIN32
#endif // _WIN32
try {
// Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider
// the connection string attributes to set (enable/disable) connection pooling.
// Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider
// the connection string attributes to set (enable/disable) connection pooling.
// Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section.
#ifndef _WIN32
char pooling_string[ 128 ] = {0};
SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" );
@ -127,28 +133,28 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
// check the connection pooling setting to determine which henv to use to allocate the connection handle
// we do this earlier because we have to allocate the connection handle prior to setting attributes on
// it in build_connection_string_and_set_conn_attr.
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
zval* option_z = NULL;
option_z = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING );
if ( option_z ) {
// if the option was found and it's not true, then use the non pooled environment handle
if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) {
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
zval* option_z = NULL;
option_z = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING );
if ( option_z ) {
// if the option was found and it's not true, then use the non pooled environment handle
if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) {
henv = &henv_ncp;
is_pooled = false;
}
}
}
#endif // !_WIN32
#endif // !_WIN32
SQLHANDLE temp_conn_h;
core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC );
conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC );
conn->set_func( driver_func );
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC );
// If column encryption is enabled, must use ODBC driver 17
if( conn->ce_option.enabled && conn->driver_version != ODBC_DRIVER_UNKNOWN) {
CHECK_CUSTOM_ERROR( conn->driver_version != ODBC_DRIVER_17, conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch() ) {
@ -157,7 +163,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
}
// In non-Windows environment, unixODBC 2.3.4 and unixODBC 2.3.1 return different error states when an ODBC driver exists or not
// Therefore, it is unreliable to check for a certain sql state error
// Therefore, it is unreliable to check for a certain sql state error
#ifndef _WIN32
if( conn->driver_version != ODBC_DRIVER_UNKNOWN ) {
// check if the ODBC driver actually exists, if not, throw an exception
@ -169,7 +175,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
}
else {
if( conn->ce_option.enabled ) {
// driver not specified, so check if ODBC 17 exists
// driver not specified, so check if ODBC 17 exists
CHECK_CUSTOM_ERROR( ! core_search_odbc_driver_unix( ODBC_DRIVER_17 ), conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch()) {
throw core::CoreException();
}
@ -184,7 +190,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
DRIVER_VERSION odbc_version = ODBC_DRIVER_UNKNOWN;
if( core_search_odbc_driver_unix( ODBC_DRIVER_17 ) ) {
odbc_version = ODBC_DRIVER_17;
}
}
else if ( core_search_odbc_driver_unix( ODBC_DRIVER_13 ) ) {
odbc_version = ODBC_DRIVER_13;
}
@ -222,7 +228,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[i];
r = core_odbc_connect( conn, conn_str_driver, is_pooled );
if( SQL_SUCCEEDED( r ) || ! core_compare_error_state( conn, r, "IM002" ) ) {
if( SQL_SUCCEEDED( r ) || ! core_compare_error_state( conn, r, "IM002" ) ) {
// something else went wrong, exit the loop now other than ODBC driver not found
done = true;
}
@ -232,10 +238,10 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
throw core::CoreException();
}
}
} // for
} // for
} // else ce_option enabled
} // else driver_version not unknown
#endif // !_WIN32
#endif // !_WIN32
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
@ -245,7 +251,9 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
throw core::CoreException();
}
// determine the version of the server we're connected to. The server version is left in the
load_azure_key_vault( conn );
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
//
// unixODBC 2.3.1:
@ -254,11 +262,11 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
// Thus, in Linux, we don't call determine_server_version() for a connection that uses pool.
#ifndef _WIN32
if ( r == SQL_SUCCESS_WITH_INFO ) {
#endif // !_WIN32
#endif // !_WIN32
determine_server_version( conn TSRMLS_CC );
#ifndef _WIN32
}
#endif // !_WIN32
#endif // !_WIN32
}
catch( std::bad_alloc& ) {
conn_str.clear();
@ -280,7 +288,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
catch( core::CoreException& ) {
conn_str.clear();
conn->invalidate();
throw;
throw;
}
conn_str.clear();
@ -293,13 +301,13 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
// core_compare_error_state
// This method compares the error state to the one specified
// Parameters:
// conn - the connection structure on which we establish the connection
// conn - the connection structure on which we establish the connection
// rc - ODBC return code
// Return - a boolean flag that indicates if the error states are the same
bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ const char* error_state )
{
if( SQL_SUCCEEDED( rc ) )
if( SQL_SUCCEEDED( rc ) )
return false;
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { 0 };
@ -310,7 +318,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_
}
// core_search_odbc_driver_unix
// This method is meant to be used in a non-Windows environment,
// This method is meant to be used in a non-Windows environment,
// searching for a particular ODBC driver name in the odbcinst.ini file
// Parameters:
// driver_version - a valid value in enum DRIVER_VERSION
@ -320,7 +328,7 @@ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version )
{
#ifndef _WIN32
char szBuf[DEFAULT_CONN_STR_LEN+1]; // use a large enough buffer size
WORD cbBufMax = DEFAULT_CONN_STR_LEN;
WORD cbBufMax = DEFAULT_CONN_STR_LEN;
WORD cbBufOut;
char *pszBuf = szBuf;
@ -330,7 +338,7 @@ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version )
return false;
}
// extract the ODBC driver name
// extract the ODBC driver name
std::string driver = CONNECTION_STRING_DRIVER_NAME[driver_version];
std::size_t pos1 = driver.find_first_of("{");
std::size_t pos2 = driver.find_first_of("}");
@ -348,7 +356,7 @@ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version )
pszBuf = strchr( pszBuf, '\0' ) + 1;
}
while( pszBuf[1] != '\0' ); // end when there are two consecutive null characters
#endif // !_WIN32
#endif // !_WIN32
return false;
}
@ -356,9 +364,9 @@ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version )
// core_odbc_connect
// calls odbc connect API to establish the connection to server
// Parameters:
// conn - The connection structure on which we establish the connection
// conn_str - Connection string
// is_pooled - indicate whether it is a pooled connection
// conn - The connection structure on which we establish the connection
// conn_str - Connection string
// is_pooled - indicate whether it is a pooled connection
// Return - SQLRETURN status returned by SQLDriverConnect
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled )
@ -388,9 +396,9 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con
}
#else
r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
#endif // !_WIN32
#endif // !_WIN32
// clear the connection string from memory
// clear the connection string from memory
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
@ -401,12 +409,12 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con
// core_sqlsrv_begin_transaction
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
// the call to core_sqlsrv_begin_transaction and before any calls to
// the call to core_sqlsrv_begin_transaction and before any calls to
// core_sqlsrv_rollback or core_sqlsrv_commit.
// The default transaction mode is auto-commit. This means that all queries
// are automatically committed upon success unless they have been designated
// as part of an explicit transaction by using core_sqlsrv_begin_transaction.
// Parameters:
// Parameters:
// sqlsrv_conn*: The connection with which the transaction is associated.
void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
@ -414,8 +422,8 @@ void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ),
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
@ -428,19 +436,19 @@ void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
// core_sqlsrv_commit.
// core_sqlsrv_commit.
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
@ -462,12 +470,12 @@ void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER TSRMLS_CC );
}
catch ( core::CoreException& ) {
throw;
@ -475,7 +483,7 @@ void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
}
// core_sqlsrv_close
// Called when a connection resource is destroyed by the Zend engine.
// Called when a connection resource is destroyed by the Zend engine.
// Parameters:
// conn - The current active connection.
void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC )
@ -485,7 +493,7 @@ void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC )
return;
try {
// rollback any transaction in progress (we don't care about the return result)
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
}
@ -495,7 +503,7 @@ void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC )
// disconnect from the server
SQLRETURN r = SQLDisconnect( conn->handle() );
if( !SQL_SUCCEEDED( r )) {
if( !SQL_SUCCEEDED( r )) {
LOG( SEV_ERROR, "Disconnect failed when closing the connection." );
}
@ -517,15 +525,15 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c
try {
// convert the string from its encoding to UTf-16
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<SQLWCHAR*>( sqlsrv_malloc( sizeof( SQLWCHAR )));
wsql_string[0] = L'\0';
wsql_len = 0;
}
}
else {
if( sql_len > INT_MAX ) {
LOG( SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
@ -561,7 +569,7 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c
throw;
}
}
}
// core_sqlsrv_get_server_version
// Determines the vesrion of the SQL Server we are connected to. Calls a helper function
@ -573,7 +581,7 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c
void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* server_version TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
@ -584,7 +592,7 @@ void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* se
}
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
@ -613,19 +621,19 @@ void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_
core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
// Get the server version
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
buffer.transferred();
// Get the server name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
buffer.transferred();
}
catch( core::CoreException& ) {
throw;
}
@ -643,7 +651,7 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
// Get the ODBC driver's dll name
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
@ -669,7 +677,7 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_
core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
buffer.transferred();
}
catch( core::CoreException& ) {
@ -680,12 +688,12 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_
// core_is_conn_opt_value_escaped
// determine if connection string value is properly escaped.
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
// the value will be surrounded by { and } by the caller after it has been validated
bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len )
{
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// unquoted since we quote it when adding it to the connection string.
if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
++value;
@ -725,12 +733,12 @@ bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t
namespace {
connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN key,
connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN key,
_In_ const connection_option conn_opts[] TSRMLS_DC )
{
for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) {
if( key == conn_opts[ opt_idx ].conn_option_key ) {
if( key == conn_opts[ opt_idx ].conn_option_key ) {
return &conn_opts[ opt_idx ];
}
@ -745,41 +753,41 @@ connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN
// passed to the connection, and then break them out ourselves and either set attributes or put the
// option in the connection string.
void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options, _In_ const connection_option valid_conn_opts[],
void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options, _In_ const connection_option valid_conn_opts[],
void* driver, _Inout_ std::string& connection_string TSRMLS_DC )
{
bool mars_mentioned = false;
connection_option const* conn_opt;
try {
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC );
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.
if(uid == NULL || strlen( uid ) == 0 ) {
if(uid == NULL || strnlen_s( uid ) == 0 ) {
connection_string += "Trusted_Connection={Yes};";
}
else {
bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid ));
bool escaped = core_is_conn_opt_value_escaped( uid, strnlen_s( uid ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC );
common_conn_str_append_func( ODBCConnOptions::UID, uid, strnlen_s( uid ), connection_string TSRMLS_CC );
// if no password was given, then don't add a password to the connection string. Perhaps the UID
// given doesn't have a password?
if( pwd != NULL ) {
escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd ));
escaped = core_is_conn_opt_value_escaped( pwd, strnlen_s( pwd ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC );
common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strnlen_s( pwd ), connection_string TSRMLS_CC );
}
}
@ -789,16 +797,16 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
return;
}
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
// if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn"
// flag is set to false.
if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) {
zval* trace_value = NULL;
zval* trace_value = NULL;
trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON);
if (trace_value == NULL || !zend_is_true(trace_value)) {
zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE );
}
}
@ -841,7 +849,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC )
{
try {
sqlsrv_malloc_auto_ptr<char> buffer;
SQLSMALLINT buffer_len = 0;
@ -859,7 +867,7 @@ void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len)
// get_processor_arch
// Calls GetSystemInfo to verify the what architecture of the processor is supported
// Calls GetSystemInfo to verify the what architecture of the processor is supported
// and return the string of the processor name.
const char* get_processor_arch( void )
{
@ -932,6 +940,85 @@ void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
conn->server_version = version_major;
}
void load_azure_key_vault(_Inout_ sqlsrv_conn* conn TSRMLS_DC)
{
// If column encryption is not enabled simply do nothing. Otherwise, check if Azure Key Vault
// is required for encryption or decryption. Note, in order to load and configure Azure Key Vault,
// all fields in conn->ce_option must be defined.
if (!conn->ce_option.enabled || !conn->ce_option.akv_required)
return;
CHECK_CUSTOM_ERROR(conn->ce_option.akv_mode == -1, conn, SQLSRV_ERROR_AKV_AUTH_MISSING) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(conn->ce_option.akv_id == NULL, conn, SQLSRV_ERROR_AKV_NAME_MISSING) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(conn->ce_option.akv_secret == NULL, conn, SQLSRV_ERROR_AKV_SECRET_MISSING) {
throw core::CoreException();
}
char *akv_id = Z_STRVAL_P(conn->ce_option.akv_id);
char *akv_secret = Z_STRVAL_P(conn->ce_option.akv_secret);
unsigned int id_len = static_cast<unsigned int>(Z_STRLEN_P(conn->ce_option.akv_id));
unsigned int key_size = static_cast<unsigned int>(Z_STRLEN_P(conn->ce_option.akv_secret));
configure_azure_key_vault(conn, AKV_CONFIG_FLAGS, conn->ce_option.akv_mode, 0);
configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len);
configure_azure_key_vault(conn, AKV_CONFIG_AUTHSECRET, akv_secret, key_size);
}
void configure_azure_key_vault(sqlsrv_conn* conn, BYTE config_attr, const DWORD config_value, size_t key_size)
{
BYTE akv_data[sizeof(CEKEYSTOREDATA) + sizeof(DWORD) + 1];
CEKEYSTOREDATA *pData = reinterpret_cast<CEKEYSTOREDATA*>(akv_data);
char akv_name[] = "AZURE_KEY_VAULT";
unsigned int name_len = 15;
unsigned int wname_len = 0;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wakv_name;
wakv_name = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, akv_name, name_len, &wname_len);
CHECK_CUSTOM_ERROR(wakv_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE) {
throw core::CoreException();
}
pData->name = (wchar_t *)wakv_name.get();
pData->data[0] = config_attr;
pData->dataSize = sizeof(config_attr) + sizeof(config_value);
*reinterpret_cast<DWORD*>(&pData->data[1]) = config_value;
core::SQLSetConnectAttr(conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast<SQLPOINTER>(pData), SQL_IS_POINTER);
}
void configure_azure_key_vault(sqlsrv_conn* conn, BYTE config_attr, const char* config_value, size_t key_size)
{
BYTE akv_data[sizeof(CEKEYSTOREDATA) + MAX_CE_NAME_LEN];
CEKEYSTOREDATA *pData = reinterpret_cast<CEKEYSTOREDATA*>(akv_data);
char akv_name[] = "AZURE_KEY_VAULT";
unsigned int name_len = 15;
unsigned int wname_len = 0;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wakv_name;
wakv_name = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, akv_name, name_len, &wname_len);
CHECK_CUSTOM_ERROR(wakv_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE) {
throw core::CoreException();
}
pData->name = (wchar_t *)wakv_name.get();
pData->data[0] = config_attr;
pData->dataSize = 1 + key_size;
memcpy_s(pData->data + 1, key_size * sizeof(char), config_value, key_size);
core::SQLSetConnectAttr(conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast<SQLPOINTER>(pData), SQL_IS_POINTER);
}
void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC )
{
// wrap a connection option in a quote. It is presumed that any character that need to be escaped will
@ -961,7 +1048,7 @@ void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC )
{
{
TSRMLS_C;
}
@ -971,19 +1058,19 @@ void driver_set_func::func( _In_ connection_option const* option, _In_ zval* val
size_t val_len = Z_STRLEN_P( value );
std::string driver_option( "" );
common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option TSRMLS_CC );
conn->driver_version = ODBC_DRIVER_UNKNOWN;
for ( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST && conn->driver_version == ODBC_DRIVER_UNKNOWN; ++i ) {
std::string driver_name = CONNECTION_STRING_DRIVER_NAME[i];
if (! driver_name.compare( driver_option ) ) {
if (! driver_name.compare( driver_option ) ) {
conn->driver_version = DRIVER_VERSION( i );
}
}
CHECK_CUSTOM_ERROR( conn->driver_version == ODBC_DRIVER_UNKNOWN, conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str) {
throw core::CoreException();
}
}
conn_str += driver_option;
}
@ -1005,17 +1092,63 @@ void column_encryption_set_func::func( _In_ connection_option const* option, _In
conn_str += ";";
}
void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC)
{
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Azure Key Vault keywords accept only strings.");
size_t value_len = Z_STRLEN_P(value);
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE) {
throw core::CoreException();
}
switch (option->conn_option_key)
{
case SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION:
{
char *value_str = Z_STRVAL_P(value);
if (!stricmp(value_str, "KeyVaultPassword")) {
conn->ce_option.akv_mode = AKVCFG_AUTHMODE_PASSWORD;
} else if (!stricmp(value_str, "KeyVaultClientSecret")) {
conn->ce_option.akv_mode = AKVCFG_AUTHMODE_CLIENTKEY;
} else {
CHECK_CUSTOM_ERROR(1, conn, SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION) {
throw core::CoreException();
}
}
conn->ce_option.akv_required = true;
break;
}
case SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID:
{
conn->ce_option.akv_id = value;
conn->ce_option.akv_required = true;
break;
}
case SQLSRV_CONN_OPTION_KEYSTORE_SECRET:
{
conn->ce_option.akv_secret = value;
conn->ce_option.akv_required = true;
break;
}
default:
SQLSRV_ASSERT(false, "ce_akv_str_set_func: Invalid AKV option!");
break;
}
}
// helper function to evaluate whether a string value is true or false.
// Values = ("true" or "1") are treated as true values. Everything else is treated as false.
// Returns 1 for true and 0 for false.
size_t core_str_zval_is_true( _Inout_ zval* value_z )
{
size_t core_str_zval_is_true( _Inout_ zval* value_z )
{
SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." );
char* value_in = Z_STRVAL_P( value_z );
size_t val_len = Z_STRLEN_P( value_z );
// strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
size_t last_char = val_len - 1;
while( isspace(( unsigned char )value_in[ last_char ] )) {
@ -1029,9 +1162,9 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z )
const char VALID_TRUE_VALUE_1[] = "true";
const char VALID_TRUE_VALUE_2[] = "1";
if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) ||
( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len ))
( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len ))
) {
return 1; // true

View file

@ -56,10 +56,9 @@
// #define MultiByteToWideChar SystemLocale::ToUtf16
#define stricmp strcasecmp
#define strnicmp strncasecmp
#define strnlen_s(s) strnlen_s(s, INT_MAX)
#ifndef _WIN32
#define GetLastError() errno
@ -175,7 +174,8 @@ const int SQL_SERVER_MAX_PRECISION = 38;
const int SQL_SERVER_MAX_TYPE_SIZE = 0;
const int SQL_SERVER_MAX_PARAMS = 2100;
// increase the maximum message length to accommodate for the long error returned for operand type clash
const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 4;
// or for conversion of a long string
const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 8;
// max size of a date time string when converting from a DateTime object to a string
const int MAX_DATETIME_STRING_LEN = 256;
@ -998,7 +998,7 @@ struct sqlsrv_encoding {
bool not_for_connection;
sqlsrv_encoding( _In_ const char* iana, _In_ unsigned int code_page, _In_ bool not_for_conn = false ):
iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn )
iana( iana ), iana_len( strnlen_s( iana )), code_page( code_page ), not_for_connection( not_for_conn )
{
}
};
@ -1056,8 +1056,12 @@ struct stmt_option;
// This holds the various details of column encryption.
struct col_encryption_option {
bool enabled; // column encryption enabled, false by default
SQLINTEGER akv_mode;
zval_auto_ptr akv_id;
zval_auto_ptr akv_secret;
bool akv_required;
col_encryption_option() : enabled( false )
col_encryption_option() : enabled( false ), akv_mode(-1), akv_required( false )
{
}
};
@ -1107,14 +1111,17 @@ const char Authentication[] = "Authentication";
const char Driver[] = "Driver";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
#ifdef _WIN32
const char ColumnEncryption[] = "ColumnEncryption";
#ifdef _WIN32
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
#endif // _WIN32
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
const char KeyStoreSecret[] = "KeyStoreSecret";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_ODBC[] = "MARS_Connection";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
@ -1157,7 +1164,10 @@ enum SQLSRV_CONN_OPTIONS {
SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER,
SQLSRV_CONN_OPTION_CEKEYSTORE_NAME,
SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY,
SQLSRV_CONN_OPTION_TRANSPARANT_NETWORK_IP_RESOLUTION,
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
#ifdef _WIN32
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
@ -1220,6 +1230,10 @@ struct ce_ksp_provider_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct ce_akv_str_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC );
@ -1312,25 +1326,25 @@ struct sqlsrv_output_param {
zval* param_z;
SQLSRV_ENCODING encoding;
SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary
bool is_bool;
bool is_long;
// string output param constructor
sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len, _In_ bool is_long ) :
param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ), is_long( is_long )
sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) :
param_z(p_z), encoding(enc), param_num(num), original_buffer_len(buffer_len), is_bool(false), php_out_type(SQLSRV_PHPTYPE_INVALID)
{
}
// every other type output parameter constructor
sqlsrv_output_param( _In_ zval* p_z, _In_ int num, _In_ bool is_bool ) :
sqlsrv_output_param( _In_ zval* p_z, _In_ int num, _In_ bool is_bool, _In_ SQLSRV_PHPTYPE php_out_type) :
param_z( p_z ),
encoding( SQLSRV_ENCODING_INVALID ),
param_num( num ),
original_buffer_len( -1 ),
is_bool( is_bool ),
is_long( false )
php_out_type(php_out_type)
{
}
};
@ -1702,7 +1716,12 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
SQLSRV_ERROR_AKV_AUTH_MISSING,
SQLSRV_ERROR_AKV_NAME_MISSING,
SQLSRV_ERROR_AKV_SECRET_MISSING,
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
@ -1786,7 +1805,7 @@ inline bool call_error_handler( _Inout_ sqlsrv_context* ctx, _In_ unsigned long
inline bool is_truncated_warning( _In_ SQLCHAR* state )
{
#if defined(ZEND_DEBUG)
if( state == NULL || strlen( reinterpret_cast<char*>( state )) != 5 ) { \
if( state == NULL || strnlen_s( reinterpret_cast<char*>( state )) != 5 ) { \
DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \
}
#endif
@ -1919,7 +1938,7 @@ namespace core {
}
inline void SQLAllocHandle( _In_ SQLSMALLINT HandleType, _Inout_ sqlsrv_context& InputHandle,
_Out_ SQLHANDLE* OutputHandlePtr TSRMLS_DC )
_Out_ SQLHANDLE* OutputHandlePtr TSRMLS_DC )
{
SQLRETURN r;
r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr );

View file

@ -372,7 +372,6 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
}
bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL );
bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE );
bool zval_was_long = ( Z_TYPE_P( param_z ) == IS_LONG && php_out_type == SQLSRV_PHPTYPE_INT && (sql_type == SQL_BIGINT || sql_type == SQL_UNKNOWN_TYPE ));
// if the user asks for for a specific type for input and output, make sure the data type we send matches the data we
// type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so
// we always let that match if they want a string back.
@ -383,16 +382,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
if( zval_was_null || zval_was_bool ){
convert_to_long( param_z );
}
if( zval_was_long ){
convert_to_string( param_z );
if( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ){
encoding = SQLSRV_ENCODING_SYSTEM;
}
match = Z_TYPE_P( param_z ) == IS_STRING;
}
else{
match = Z_TYPE_P( param_z ) == IS_LONG;
}
match = Z_TYPE_P( param_z ) == IS_LONG;
break;
case SQLSRV_PHPTYPE_FLOAT:
if( zval_was_null ){
@ -431,15 +421,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
if( direction == SQL_PARAM_OUTPUT ){
switch( php_out_type ) {
case SQLSRV_PHPTYPE_INT:
if( zval_was_long ){
convert_to_string( param_z );
if( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ){
encoding = SQLSRV_ENCODING_SYSTEM;
}
}
else{
convert_to_long( param_z );
}
convert_to_long( param_z );
break;
case SQLSRV_PHPTYPE_FLOAT:
convert_to_double( param_z );
@ -509,7 +491,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ){
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool );
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool, php_out_type);
save_output_param_for_later( stmt, output_param TSRMLS_CC );
}
}
@ -521,7 +503,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
ind_ptr = buffer_len;
if( direction != SQL_PARAM_INPUT ){
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), false );
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool, php_out_type);
save_output_param_for_later( stmt, output_param TSRMLS_CC );
}
}
@ -572,7 +554,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
bool converted = convert_input_param_to_utf16( param_z, param_z );
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
param_num + 1, get_last_error_message() ){
param_num + 1, get_last_error_message() ){
throw core::CoreException();
}
buffer = Z_STRVAL_P( param_z );
@ -583,10 +565,10 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
// since this is an output string, assure there is enough space to hold the requested size and
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, decimal_digits,
buffer, buffer_len TSRMLS_CC );
buffer, buffer_len TSRMLS_CC );
// save the parameter to be adjusted and/or converted after the results are processed
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ), zval_was_long );
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ) );
save_output_param_for_later( stmt, output_param TSRMLS_CC );
@ -1874,16 +1856,16 @@ SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno,
break;
case IS_TRUE:
case IS_FALSE:
sql_c_type = SQL_C_SLONG;
break;
sql_c_type = SQL_C_SLONG;
break;
case IS_LONG:
//ODBC 64-bit long and integer type are 4 byte values.
if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) {
sql_c_type = SQL_C_SBIGINT;
}
else {
sql_c_type = SQL_C_SLONG;
}
//ODBC 64-bit long and integer type are 4 byte values.
if ((Z_LVAL_P(param_z) < INT_MIN) || (Z_LVAL_P(param_z) > INT_MAX)) {
sql_c_type = SQL_C_SBIGINT;
}
else {
sql_c_type = SQL_C_SLONG;
}
break;
case IS_DOUBLE:
sql_c_type = SQL_C_DOUBLE;
@ -1947,13 +1929,13 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_
sql_type = SQL_INTEGER;
break;
case IS_LONG:
//ODBC 64-bit long and integer type are 4 byte values.
if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) {
sql_type = SQL_BIGINT;
}
else {
sql_type = SQL_INTEGER;
}
//ODBC 64-bit long and integer type are 4 byte values.
if ((Z_LVAL_P(param_z) < INT_MIN) || (Z_LVAL_P(param_z) > INT_MAX)) {
sql_type = SQL_BIGINT;
}
else {
sql_type = SQL_INTEGER;
}
break;
case IS_DOUBLE:
sql_type = SQL_FLOAT;
@ -2151,15 +2133,6 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
else {
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
if ( output_param->is_long ) {
zval* value_z_temp = ( zval * )sqlsrv_malloc( sizeof( zval ));
ZVAL_COPY( value_z_temp, value_z );
convert_to_double( value_z_temp );
if ( Z_DVAL_P( value_z_temp ) > INT_MIN && Z_DVAL_P( value_z_temp ) < INT_MAX ) {
convert_to_long( value_z );
}
sqlsrv_free( value_z_temp );
}
}
break;
case IS_LONG:
@ -2176,8 +2149,23 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
break;
case IS_DOUBLE:
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
ZVAL_NULL( value_z );
if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) {
ZVAL_NULL(value_z);
}
else if (output_param->php_out_type == SQLSRV_PHPTYPE_INT) {
// first check if its value is out of range
double dval = Z_DVAL_P(value_z);
if (dval > INT_MAX || dval < INT_MIN) {
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED) {
throw core::CoreException();
}
}
// if the output param is a boolean, still convert to
// a long integer first to take care of rounding
convert_to_long(value_z);
if (output_param->is_bool) {
convert_to_boolean(value_z);
}
}
break;
default:
@ -2535,11 +2523,17 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval*
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
SQLULEN field_size = column_size;
// with AE on, when column_size is retrieved from SQLDescribeParam, column_size does not include the decimal place
// include the decimal for output params by adding elem_size
if ( stmt->conn->ce_option.enabled && decimal_digits > 0 )
{
// with AE on, when column_size is retrieved from SQLDescribeParam, column_size
// does not include the negative sign or decimal place for numeric values
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
// and numerics with precision/scale specified
if (sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC || sql_type == SQL_BIGINT || sql_type == SQL_INTEGER || sql_type == SQL_SMALLINT) {
// include the possible negative sign
field_size += elem_size;
// include the decimal for output params by adding elem_size
if (decimal_digits > 0) {
field_size += elem_size;
}
}
if (column_size == SQL_SS_LENGTH_UNLIMITED) {
field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size;

View file

@ -310,7 +310,7 @@ SystemLocale::SystemLocale( const char * localeName )
charsetName = charsetName ? charsetName + 1 : localeName;
for (const LocaleCP& lcp : lcpTable)
{
if (!strncasecmp(lcp.localeName, charsetName, strlen(lcp.localeName)))
if (!strncasecmp(lcp.localeName, charsetName, strnlen_s(lcp.localeName)))
{
m_uAnsiCP = lcp.codePage;
return;
@ -346,7 +346,7 @@ size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc);
size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc);
bool hasLoss;
return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode );
}
@ -361,7 +361,7 @@ size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc);
size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc);
bool hasLoss;
return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode );
}

View file

@ -22,20 +22,18 @@
#define STRINGIFY(a) TOSTRING(a)
#define TOSTRING(a) #a
// Increase Major number with backward incompatible breaking changes.
// Increase Minor with backward compatible new functionalities and API changes.
// Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 2
#define SQLVERSION_PATCH 0
#define SQLVERSION_PATCH 1
#define SQLVERSION_BUILD 0
// Semantic versioning pre-release
// for stable releases should be empty
// "-RC" for release candidates
// "-preview" for ETP
// For previews, set this constant to 1. Otherwise, set it to 0
#define PREVIEW 1
#define SEMVER_PRERELEASE
// Semantic versioning build metadata, build meta data is not counted in precedence order.
#define SEMVER_BUILDMETA
@ -47,13 +45,22 @@
// Main version, dot separated 3 digits, Major.Minor.Patch
#define VER_APIVERSION_STR STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_PATCH )
// For preview release, we want the following:
// #define VER_FILEVERSION_STR VER_APIVERSION_STR "-" SEMVER_PRERELEASE SEMVER_BUILDMETA
// because pecl doesn't like dashes. However, if SEMVER_PRERELEASE is empty, the "-" must be removed
// Semantic versioning:
// For stable releases leave SEMVER_PRERELEASE empty
// Otherwise, for pre-releases, add '-' and change it to:
// "RC" for release candidates
// "preview" for ETP
#if PREVIEW > 0
#undef SEMVER_PRERELEASE
#define SEMVER_PRERELEASE "preview"
#define VER_FILEVERSION_STR VER_APIVERSION_STR "-" SEMVER_PRERELEASE SEMVER_BUILDMETA
#else
#define VER_FILEVERSION_STR VER_APIVERSION_STR SEMVER_PRERELEASE SEMVER_BUILDMETA
#endif
#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD
// PECL package version macros (can't have '-' or '+')
// PECL package version macros ('-' or '+' is not allowed)
#define PHP_SQLSRV_VERSION VER_APIVERSION_STR SEMVER_PRERELEASE
#define PHP_PDO_SQLSRV_VERSION PHP_SQLSRV_VERSION

View file

@ -184,19 +184,22 @@ namespace SSConnOptionNames {
const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char CharacterSet[] = "CharacterSet";
const char Authentication[] = "Authentication";
const char ConnectionPooling[] = "ConnectionPooling";
const char Driver[] = "Driver";
#ifdef _WIN32
const char CharacterSet[] = "CharacterSet";
const char ColumnEncryption[] = "ColumnEncryption";
const char ConnectionPooling[] = "ConnectionPooling";
#ifdef _WIN32
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
#endif // _WIN32
const char Database[] = "Database";
const char DateAsString[] = "ReturnDatesAsStrings";
const char Driver[] = "Driver";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
const char KeyStoreSecret[] = "KeyStoreSecret";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_Option[] = "MultipleActiveResultSets";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
@ -312,7 +315,6 @@ const connection_option SS_CONN_OPTS[] = {
CONN_ATTR_STRING,
driver_set_func::func
},
#ifdef _WIN32
{
SSConnOptionNames::ColumnEncryption,
sizeof(SSConnOptionNames::ColumnEncryption),
@ -322,6 +324,7 @@ const connection_option SS_CONN_OPTS[] = {
CONN_ATTR_STRING,
column_encryption_set_func::func
},
#ifdef _WIN32
{
SSConnOptionNames::ConnectRetryCount,
sizeof( SSConnOptionNames::ConnectRetryCount ),
@ -368,6 +371,33 @@ const connection_option SS_CONN_OPTS[] = {
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::KeyStoreAuthentication,
sizeof( SSConnOptionNames::KeyStoreAuthentication ),
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
ODBCConnOptions::KeyStoreAuthentication,
sizeof( ODBCConnOptions::KeyStoreAuthentication ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::KeyStorePrincipalId,
sizeof( SSConnOptionNames::KeyStorePrincipalId ),
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
ODBCConnOptions::KeyStorePrincipalId,
sizeof( ODBCConnOptions::KeyStorePrincipalId ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::KeyStoreSecret,
sizeof( SSConnOptionNames::KeyStoreSecret ),
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
ODBCConnOptions::KeyStoreSecret,
sizeof( ODBCConnOptions::KeyStoreSecret ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::LoginTimeout,
sizeof( SSConnOptionNames::LoginTimeout ),
@ -443,7 +473,7 @@ const connection_option SS_CONN_OPTS[] = {
{
SSConnOptionNames::TransparentNetworkIPResolution,
sizeof(SSConnOptionNames::TransparentNetworkIPResolution),
SQLSRV_CONN_OPTION_TRANSPARANT_NETWORK_IP_RESOLUTION,
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
ODBCConnOptions::TransparentNetworkIPResolution,
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
CONN_ATTR_STRING,
@ -525,7 +555,7 @@ PHP_FUNCTION ( sqlsrv_connect )
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, ss_conn_options_ht, 10 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
// Either of g_ss_henv_cp or g_ss_henv_ncp can be used to propogate the error.
// Either of g_ss_henv_cp or g_ss_henv_ncp can be used to propagate the error.
::validate_conn_options( *g_ss_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC );
// call the core connect function

View file

@ -397,8 +397,28 @@ ss_error SS_ERRORS[] = {
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
},
{
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -109, false }
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"Error converting a double (value out of range) to an integer.", -109, false }
},
{
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the KeyStoreAuthentication keyword. Only KeyVaultPassword or KeyVaultClientSecret is allowed.", -110, false }
},
{
SQLSRV_ERROR_AKV_AUTH_MISSING,
{ IMSSP, (SQLCHAR*) "The authentication method for Azure Key Vault is missing. KeyStoreAuthentication must be set to KeyVaultPassword or KeyVaultClientSecret.", -111, false }
},
{
SQLSRV_ERROR_AKV_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The username or client Id for Azure Key Vault is missing.", -112, false }
},
{
SQLSRV_ERROR_AKV_SECRET_MISSING,
{ IMSSP, (SQLCHAR*) "The password or client secret for Azure Key Vault is missing.", -113, false }
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -114, false}
},
// terminate the list of errors/warnings

View file

@ -1,56 +1,76 @@
--TEST--
insert stream.
--SKIPIF--
?>
--FILE--
<?php
/* Connect to the local server using Windows Authentication and
specify the AdventureWorks database as the database in use. */
require('connect.inc');
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$conn = sqlsrv_connect( $server, $connectionInfo);
if( $conn === false )
{
echo "Could not connect.\n";
die( print_r( sqlsrv_errors(), true));
}
/* Set up the Transact-SQL query. */
$tsql = "INSERT INTO Production.ProductReview (ProductID,
ReviewerName,
ReviewDate,
EmailAddress,
Rating,
Comments)
VALUES (?, ?, ?, ?, ?, ?)";
/* Set the parameter values and put them in an array.
Note that $comments is opened as a stream. */
$productID = '709';
$name = 'Customer Name';
$date = date("Y-m-d");
$email = 'customer@name.com';
$rating = 3;
$comments = fopen( "data://text/plain,[ Insert lengthy comment here.]",
"r");
$params = array($productID, $name, $date, $email, $rating, $comments);
/* Execute the query. All stream data is sent upon execution.*/
$stmt = sqlsrv_query($conn, $tsql, $params);
if( $stmt === false )
{
echo "Error in statement execution.\n";
die( print_r( sqlsrv_errors(), true));
}
else
{
echo "The query was successfully executed.";
}
/* Free statement and connection resources. */
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--
The query was successfully executed.
--TEST--
insert stream.
--SKIPIF--
--FILE--
<?php
/* Connect to the local server using Windows Authentication and
specify the AdventureWorks database as the database in use. */
require('connect.inc');
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
echo "Could not connect.\n";
die(print_r(sqlsrv_errors(), true));
}
/* Remove any records with from the table with ProductID = 999*/
$productID = 999;
$tsql = "DELETE FROM Production.ProductReview WHERE ProductID = $productID";
sqlsrv_query($conn, $tsql);
/* Set up the Transact-SQL query. */
$tsql = "INSERT INTO Production.ProductReview (ProductID,
ReviewerName,
ReviewDate,
EmailAddress,
Rating,
Comments)
VALUES (?, ?, ?, ?, ?, ?)";
/* Set the parameter values and put them in an array.
Note that $comments is opened as a stream. */
$number = rand(99, 9999);
$input = "[Insert some number $number]";
/* There is no record in this table with ProductID = 999 */
$name = 'Customer Name';
$date = date("Y-m-d");
$email = 'customer@name.com';
$rating = 3;
$comments = fopen("data://text/plain,$input", "r");
$params = array($productID, $name, $date, $email, $rating, $comments);
/* Execute the query. All stream data is sent upon execution.*/
$stmt = sqlsrv_query($conn, $tsql, $params);
if ($stmt === false) {
echo "Error in statement execution.\n";
die(print_r(sqlsrv_errors(), true));
}
/* Read it back to check the comment */
$tsql = "SELECT Comments FROM Production.ProductReview
WHERE ProductID = $productID";
$stmt = sqlsrv_query($conn, $tsql);
if (sqlsrv_fetch($stmt)) {
$review = sqlsrv_get_field($stmt, 0);
if ($review !== $input) {
echo "Comment retrieved \'$review\' is incorrect!" . PHP_EOL;
}
} else {
echo "Error in retrieving comments!" . PHP_EOL;
die(print_r(sqlsrv_errors(), true));
}
/* Remove the entry from the table */
$tsql = "DELETE FROM Production.ProductReview WHERE ProductID = $productID";
sqlsrv_query($conn, $tsql);
echo "Done" . PHP_EOL;
/* Free statement and connection resources. */
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECT--
Done

View file

@ -1,48 +1,69 @@
--TEST--
Sends data from parameter streams to the server
--SKIPIF--
--FILE--
--TEST--
Sends data from parameter streams to the server
--SKIPIF--
--FILE--
<?php
require('connect.inc');
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$conn = sqlsrv_connect( $server, $connectionInfo);
if( $conn === false )
{
echo "Could not connect.<br>";
die( print_r( sqlsrv_errors(), true));
$connectionInfo = array("Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
echo "Could not connect.<br>";
die(print_r(sqlsrv_errors(), true));
}
/* Define the query. */
$tsql = "UPDATE Production.ProductReview
SET Comments = ( ?)
$tsql = "UPDATE Production.ProductReview
SET Comments = (?)
WHERE ProductReviewID = 3";
$number = rand(99, 9999);
$input = "[Insert some number $number]";
/* Open parameter data as a stream and put it in the $params array. */
$comment = fopen( "data://text/plain,[ Insert lengthy comment.]", "r");
$params = array( &$comment);
$comments = fopen("data://text/plain,$input", "r");
$params = array(&$comments);
/* Prepare the statement. Use the $options array to turn off the
default behavior, which is to send all stream data at the time of query
execution. */
$options = array("SendStreamParamsAtExec"=>0);
$stmt = sqlsrv_prepare( $conn, $tsql, $params, $options);
$options = array("SendStreamParamsAtExec" => 0);
$stmt = sqlsrv_prepare($conn, $tsql, $params, $options);
/* Execute the statement. */
sqlsrv_execute( $stmt);
sqlsrv_execute($stmt);
/* Send up to 8K of parameter data to the server with each call to
sqlsrv_send_stream_data. Count the calls. */
$i = 1;
while( sqlsrv_send_stream_data( $stmt))
{
echo "$i call(s) made.<br>";
$i++;
}
$i = 0;
while (sqlsrv_send_stream_data($stmt)) {
$i++;
}
/* For PHP 7.2, it takes 2 calls whereas older PHP versions
take up to 3 calls */
if ($i < 2 || $i > 3) {
echo "Expects 2 to 3 calls only." . PHP_EOL;
}
/* Read it back to check the comments */
$tsql = "SELECT Comments FROM Production.ProductReview
WHERE ProductReviewID = 3";
$stmt = sqlsrv_query($conn, $tsql);
if (sqlsrv_fetch($stmt)) {
$review = sqlsrv_get_field($stmt, 0);
if ($review !== $input) {
echo "Comments retrieved \'$review\' is incorrect!" . PHP_EOL;
}
} else {
echo "Error in retrieving comments!" . PHP_EOL;
die(print_r(sqlsrv_errors(), true));
}
echo "Done" . PHP_EOL;
/* Free statement and connection resources. */
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--
1 call(s) made.<br>2 call(s) made.<br>3 call(s) made.<br>
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECT--
Done

View file

@ -1,39 +1,43 @@
--TEST--
sned stream data with SendStreamParamsAtExec turned off.
--SKIPIF--
?>
--FILE--
--TEST--
Send stream data with SendStreamParamsAtExec turned off.
--SKIPIF--
--FILE--
<?php
/* Connect to the local server using Windows Authentication and
specify the AdventureWorks database as the database in use. */
require('connect.inc');
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$conn = sqlsrv_connect( $server, $connectionInfo);
if( $conn === false )
{
echo "Could not connect.\n";
die( print_r( sqlsrv_errors(), true));
$connectionInfo = array("Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
echo "Could not connect.\n";
die(print_r(sqlsrv_errors(), true));
}
/* Remove any records with from the table with ProductID = 999*/
$productID = 999;
$tsql = "DELETE FROM Production.ProductReview WHERE ProductID = $productID";
sqlsrv_query($conn, $tsql);
/* Set up the Transact-SQL query. */
$tsql = "INSERT INTO Production.ProductReview (ProductID,
ReviewerName,
ReviewDate,
EmailAddress,
Rating,
Comments)
$tsql = "INSERT INTO Production.ProductReview (ProductID,
ReviewerName,
ReviewDate,
EmailAddress,
Rating,
Comments)
VALUES (?, ?, ?, ?, ?, ?)";
/* Set the parameter values and put them in an array.
Note that $comments is opened as a stream. */
$productID = '709';
Note that $comments is opened as a stream. */
$number = rand(99, 9999);
$input = "[Insert some number $number]";
/* There is no record in this table with ProductID 999 */
$name = 'Customer Name';
$date = date("Y-m-d");
$email = 'customer@name.com';
$rating = 3;
$comments = fopen( "data://text/plain,[ Insert lengthy comment here.]",
"r");
$comments = fopen("data://text/plain,$input", "r");
$params = array($productID, $name, $date, $email, $rating, $comments);
/* Turn off the default behavior of sending all stream data at
@ -42,26 +46,47 @@ $options = array("SendStreamParamsAtExec" => 0);
/* Execute the query. */
$stmt = sqlsrv_query($conn, $tsql, $params, $options);
if( $stmt === false )
{
echo "Error in statement execution.\n";
die( print_r( sqlsrv_errors(), true));
if ($stmt === false) {
echo "Error in statement execution.\n";
die(print_r(sqlsrv_errors(), true));
}
/* Send up to 8K of parameter data to the server with each call to
sqlsrv_send_stream_data. Count the calls. */
$i = 1;
while( sqlsrv_send_stream_data( $stmt))
{
echo "$i call(s) made.\n";
$i++;
$i = 0;
while (sqlsrv_send_stream_data($stmt)) {
$i++;
}
/* For PHP 7.2, it takes 2 calls whereas older PHP versions
take up to 3 calls */
if ($i < 2 || $i > 3) {
echo "Expects 2 to 3 calls only." . PHP_EOL;
}
/* Read it back to check the comments */
$tsql = "SELECT Comments FROM Production.ProductReview
WHERE ProductID = $productID";
$stmt = sqlsrv_query($conn, $tsql);
if (sqlsrv_fetch($stmt)) {
$review = sqlsrv_get_field($stmt, 0);
if ($review !== $input) {
echo "Comments retrieved \'$review\' is incorrect!" . PHP_EOL;
}
} else {
echo "Error in retrieving comments!" . PHP_EOL;
die(print_r(sqlsrv_errors(), true));
}
/* Remove the entry from the table */
$tsql = "DELETE FROM Production.ProductReview WHERE ProductID = $productID";
sqlsrv_query($conn, $tsql);
echo "Done" . PHP_EOL;
/* Free statement and connection resources. */
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--
1 call(s) made.
2 call(s) made.
3 call(s) made.
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECT--
Done

View file

@ -92,6 +92,13 @@ function getDSN($sqlsrvserver, $database, $keywords = '', $disableCE = false)
if ($keystore != "none" && !$disableCE) {
$dsn .= "ColumnEncryption=Enabled;";
}
if ($keystore == "akv" && !$disableCE) {
if ($AKVKeyStoreAuthentication == "KeyVaultPassword") {
$dsn .= "KeyStoreAuthentication=$AKVKeyStoreAuthentication;KeyStorePrincipalId=$AKVPrincipalName;KeyStoreSecret=$AKVPassword;";
} else if ($AKVKeyStoreAuthentication == "KeyVaultClientSecret") {
$dsn .= "KeyStoreAuthentication=$AKVKeyStoreAuthentication;KeyStorePrincipalId=$AKVClientID;KeyStoreSecret=$AKVSecret;";
}
}
if ($keystore == "ksp" && !$disableCE) {
$ksp_path = getKSPPath();
$ksp_name = KSP_NAME;

View file

@ -44,4 +44,11 @@ $traceEnabled = false;
$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv
$dataEncrypted = false; // whether data is to be encrypted
// for Azure Key Vault
$AKVKeyStoreAuthentication = 'TARGET_AKV_AUTH'; // can be KeyVaultPassword or KeyVaultClientSecret
$AKVPrincipalName = 'TARGET_AKV_PRINCIPAL_NAME'; // for use with KeyVaultPassword
$AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPassword
$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret
$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret
?>

View file

@ -0,0 +1,136 @@
--TEST--
GitHub issue #678 - Idle Connection Resiliency doesn't work with Connection Pooling
--DESCRIPTION--
Verifies that the issue has been fixed with ODBC 17.1
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_protocol_not_tcp.inc');
require('skipif_version_less_than_2k14.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function checkODBCVersion($conn)
{
$msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"];
$vers = explode(".", $msodbcsql_ver);
if ($vers[0] >= 17 && $vers[1] > 0){
return true;
} else {
return false;
}
}
function breakConnection($conn, $conn_break)
{
try {
$stmt1 = $conn->query("SELECT @@SPID");
$obj = $stmt1->fetch(PDO::FETCH_NUM);
$spid = $obj[0];
$stmt2 = $conn_break->prepare("KILL $spid");
$stmt2->execute();
sleep(1);
} catch (Exception $e) {
print_r($e->getMessage());
}
}
// create a connection for create the table and breaking other connections
$conn_break = connect();
if (! checkODBCVersion($conn_break)) {
echo "Done\n";
return;
}
$tableName = "test_connres";
dropTable($conn_break, $tableName);
try {
$sql = "CREATE TABLE $tableName (c1 INT, c2 VARCHAR(40))";
$stmt = $conn_break->query($sql);
$sql = "INSERT INTO $tableName VALUES (?, ?)";
$stmt = $conn_break->prepare($sql);
for ($t = 200; $t < 209; $t++) {
$ts = substr(sha1($t), 0, 5);
$stmt->bindValue(1, $t);
$stmt->bindValue(2, $ts);
$stmt->execute();
}
} catch (PDOException $e) {
echo "Could not connect.\n";
print_r($e->getMessage());
}
// first connection
$connectionInfo = "ConnectRetryCount = 10; ConnectRetryInterval = 10; ConnectionPooling = 1;";
try {
$conn = connect($connectionInfo, array(), PDO::ERRMODE_EXCEPTION, true);
} catch (PDOException $e) {
echo "Error in connection 1.\n";
print_r($e->getMessage());
}
breakConnection($conn, $conn_break);
$query = "SELECT * FROM $tableName";
try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED));
if (!$stmt || !$stmt->execute()) {
echo "Statement 1 failed.\n";
}
$row_count = $stmt->rowCount();
if ($row_count != 9) {
echo "Unexpected $row_count rows in result set.\n";
}
} catch (PDOException $e) {
echo "Error executing query with connection 1.\n";
print_r($e->getMessage());
}
unset($stmt);
unset($conn);
// second connection
try {
$conn = connect($connectionInfo, array(), PDO::ERRMODE_EXCEPTION, true);
} catch (PDOException $e) {
echo "Error in connection 2.\n";
print_r($e->getMessage());
}
breakConnection($conn, $conn_break);
// would connection be able to resume here if connection pooling is enabled?
try {
$stmt2 = $conn->query($query);
if (!$stmt2) {
echo "Statement 2 failed.\n";
}
$col_count = $stmt2->columnCount();
if ($col_count != 2) {
echo "Unexpected $col_count columns in result set.\n";
}
} catch (PDOException $e) {
echo "Error executing query with connection 2.\n";
print_r($e->getMessage());
}
dropTable($conn, $tableName);
echo "Done\n";
unset($stmt2);
unset($conn);
unset($conn_break);
?>
--EXPECT--
Done

View file

@ -0,0 +1,141 @@
--TEST--
GitHub issue 707 - binding decimals/numerics to integers or booleans with ColumnEncryption
--DESCRIPTION--
Verifies that the double values will be rounded as integers or returned as booleans
The key of this test is to connect with ColumnEncryption enabled, and the table columns
do not need to be encrypted
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_unix.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
$error = "Error converting a double (value out of range) to an integer";
function getOutputs($stmt, $outSql, $id, $pdoParamType, $inout = false)
{
$dec = $num = 0;
if ($inout) {
$paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT;
} else {
$paramType = $pdoParamType;
}
$stmt->bindParam(1, $id, PDO::PARAM_INT);
$stmt->bindParam(2, $dec, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(3, $num, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->execute();
if ($pdoParamType == PDO::PARAM_BOOL) {
if (!$dec || !$num) {
echo "The returned booleans ($dec, $num) were unexpected!\n";
}
} else {
if ($dec != 100 || $num != 200) {
echo "The returned integers ($dec, $num) were unexpected!\n";
}
}
}
function getOutputsWithException($stmt, $outSql, $id, $pdoParamType, $inout = false)
{
global $error;
try {
getOutputs($stmt, $outSql, $id, $pdoParamType, $inout);
} catch (PDOException $e) {
$message = $e->getMessage();
$found = strpos($message, $error);
if ($found === false) {
echo "Exception message unexpected!\n";
}
}
}
function getSmallNumbers($conn, $outSql)
{
$stmt = $conn->prepare($outSql);
getOutputs($stmt, $outSql, 1, PDO::PARAM_BOOL);
getOutputs($stmt, $outSql, 1, PDO::PARAM_INT);
getOutputs($stmt, $outSql, 1, PDO::PARAM_BOOL, true);
getOutputs($stmt, $outSql, 1, PDO::PARAM_INT, true);
unset($stmt);
}
function getHugeNumbers($conn, $outSql)
{
// Expects an exception for each call
$stmt = $conn->prepare($outSql);
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_BOOL);
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_INT);
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_BOOL, true);
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_INT, true);
unset($stmt);
}
try {
// Check eligibility
$conn = new PDO( "sqlsrv:server = $server", $uid, $pwd );
if (!isAEQualified($conn)) {
echo "Done\n";
return;
}
unset($conn);
// Connection with column encryption enabled
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tableName = "test_707_decimals";
$procName = "sp_test_707_decimals";
dropTable($conn, $tableName);
dropProc($conn, $procName);
// Create a test table
$tsql = "CREATE TABLE $tableName (id int identity(1,1), c1_decimal decimal(19,4), c2_numeric numeric(20, 6))";
$stmt = $conn->query($tsql);
unset($stmt);
// Insert two rows
$tsql = "INSERT INTO $tableName (c1_decimal, c2_numeric) VALUES (100.078, 200.034)";
$stmt = $conn->query($tsql);
unset($stmt);
$tsql = "INSERT INTO $tableName (c1_decimal, c2_numeric) VALUES (199999999999.0123, 999243876923.09887)";
$stmt = $conn->query($tsql);
unset($stmt);
// Create a stored procedure
$procArgs = "@id int, @c_decimal decimal(19,4) OUTPUT, @c_numeric numeric(20, 6) OUTPUT";
$procCode = "SELECT @c_decimal = c1_decimal, @c_numeric = c2_numeric FROM $tableName WHERE id = @id";
createProc($conn, $procName, $procArgs, $procCode);
// Read them back by calling the stored procedure
$outSql = getCallProcSqlPlaceholders($procName, 3);
getSmallNumbers($conn, $outSql);
getHugeNumbers($conn, $outSql);
dropProc($conn, $procName);
dropTable($conn, $tableName);
unset($conn);
echo "Done\n";
} catch( PDOException $e ) {
print_r( $e->getMessage() );
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,42 @@
--TEST--
Test client ID/secret credentials for Azure Key Vault for Always Encrypted.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once('pdo_ae_azure_key_vault_common.php');
// The array of data types corresponding to $small_values in values.php.
// SHORT_STRSIZE is defined in values.php as well.
$dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nvarchar(".SHORT_STRSIZE.")",
"decimal", "float", "real", "bigint", "int", "bit"
);
$connectionOptions = "sqlsrv:Server=$server;Database=$databaseName";
$connectionOptions .= ";ColumnEncryption=enabled";
$connectionOptions .= ";KeyStoreAuthentication=KeyVaultClientSecret";
$connectionOptions .= ";KeyStorePrincipalId=".$AKVClientID;
$connectionOptions .= ";KeyStoreSecret=".$AKVSecret;
$connectionOptions .= ";";
$tableName = "akv_comparison_table";
// Connect to the AE-enabled database, insert the data, and verify
try {
$conn = new PDO($connectionOptions, $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
insertDataAndVerify($conn, $tableName, $dataTypes, $small_values);
echo "Successful insertion and retrieval with client ID/secret.\n";
unset($conn);
} catch (Exception $e) {
echo "Unexpected error.\n";
print_r($e->errorInfo);
}
?>
--EXPECT--
Successful insertion and retrieval with client ID/secret.

View file

@ -0,0 +1,94 @@
<?php
require_once("MsCommon_mid-refactor.inc");
require_once("MsSetup.inc");
require_once('values.php');
// Set up the columns and build the insert query. Each data type has an
// AE-encrypted and a non-encrypted column side by side in the table.
// If column encryption is not set in MsSetup.inc, this function simply
// creates two non-encrypted columns side-by-side for each type.
function formulateSetupQuery($tableName, &$dataTypes, &$columns, &$insertQuery)
{
$columns = array();
$queryTypes = "(";
$queryTypesAE = "(";
$valuesString = "VALUES (";
$numTypes = sizeof($dataTypes);
for ($i = 0; $i < $numTypes; ++$i) {
// Replace parentheses for column names
$colname = str_replace(array("(", ",", ")"), array("_", "_", ""), $dataTypes[$i]);
$columns[] = new ColumnMeta($dataTypes[$i], "c_".$colname."_AE", null, "deterministic", false);
$columns[] = new ColumnMeta($dataTypes[$i], "c_".$colname, null, "none", false);
$queryTypes .= "c_"."$colname, ";
$queryTypes .= "c_"."$colname"."_AE, ";
$valuesString .= "?, ?, ";
}
$queryTypes = substr($queryTypes, 0, -2).")";
$valuesString = substr($valuesString, 0, -2).")";
$insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString;
}
// Create the table and insert the data, then retrieve it back and make
// sure the encrypted and non-encrypted values are identical.
function insertDataAndVerify($conn, $tableName, $dataTypes, $values)
{
$columns = array();
$insertQuery = "";
// Generate the INSERT query
formulateSetupQuery($tableName, $dataTypes, $columns, $insertQuery);
createTable($conn, $tableName, $columns);
// Duplicate all values for insertion - one is encrypted, one is not
$testValues = array();
for ($n = 0; $n < sizeof($values); ++$n) {
$testValues[] = $values[$n];
$testValues[] = $values[$n];
}
// Prepare the INSERT query
// This is never expected to fail
$stmt = $conn->prepare($insertQuery);
if ($stmt == false) {
print_r($conn->errorInfo());
fatalError("sqlsrv_prepare failed\n");
}
// Execute the INSERT query
// This should not fail since our credentials are correct
if ($stmt->execute($testValues) == false) {
print_r($stmt->errorInfo());
fatalError("INSERT query execution failed with good credentials.\n");
} else {
// Get the data back and compare encrypted and non-encrypted versions
$selectQuery = "SELECT * FROM $tableName";
$stmt1 = $conn->query($selectQuery);
$data = $stmt1->fetchAll(PDO::FETCH_NUM);
$data = $data[0];
if (sizeof($data) != 2*sizeof($dataTypes)) {
fatalError("Incorrect number of fields returned.\n");
}
for ($n = 0; $n < sizeof($data); $n += 2) {
if ($data[$n] != $data[$n + 1]) {
echo "Failed on field $n: ".$data[$n]." ".$data[$n + 1]."\n";
fatalError("AE and non-AE values do not match.\n");
}
}
unset($stmt);
unset($stmt1);
}
// Drop the table
dropTable($conn, $tableName);
}
?>

View file

@ -0,0 +1,167 @@
--TEST--
Test connection keywords for Azure Key Vault for Always Encrypted.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once('pdo_ae_azure_key_vault_common.php');
// This test only applies to Azure Key Vault, or to no encryption at all
if ($keystore != 'none' and $keystore != 'akv') {
echo "Done.\n";
exit();
}
// We will test the direct product (set of all possible combinations) of the following
$columnEncryption = ['enabled', 'disabled', 'notvalid', ''];
$keyStoreAuthentication = ['KeyVaultPassword', 'KeyVaultClientSecret', 'KeyVaultNothing', ''];
$keyStorePrincipalId = [$AKVPrincipalName, $AKVClientID, 'notaname', ''];
$keyStoreSecret = [$AKVPassword, $AKVSecret, 'notasecret', ''];
// Verify that the error is in the list of expected errors
function checkErrors($errors, ...$codes)
{
$codeFound = false;
foreach ($codes as $code) {
if ($code[0]==$errors[0] and $code[1]==$errors[1]) {
$codeFound = true;
}
}
if ($codeFound == false) {
echo "Error: ";
print_r($errors);
echo "\nExpected: ";
print_r($codes);
echo "\n";
fatalError("Error code not found.\n");
}
}
// The array of data types corresponding to $small_values in values.php.
// SHORT_STRSIZE is defined in values.php as well.
$dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nvarchar(".SHORT_STRSIZE.")",
"decimal", "float", "real", "bigint", "int", "bit"
);
$tableName = "akv_comparison_table";
// 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
// we can't run a PHP test without caching, and if we started with good
// credentials then subsequent calls with bad credentials can work, which
// would muddle the results of this test. Good credentials are tested in a
// separate test.
for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
for ($j = 0; $j < sizeof($keyStoreAuthentication); ++$j) {
for ($k = 0; $k < sizeof($keyStorePrincipalId); ++$k) {
for ($m = 0; $m < sizeof($keyStoreSecret); ++$m) {
$connectionOptions = "sqlsrv:Server=$server;Database=$databaseName";
if (!empty($columnEncryption[$i])) {
$connectionOptions .= ";ColumnEncryption=".$columnEncryption[$i];
}
if (!empty($keyStoreAuthentication[$j])) {
$connectionOptions .= ";KeyStoreAuthentication=".$keyStoreAuthentication[$j];
}
if (!empty($keyStorePrincipalId[$k])) {
$connectionOptions .= ";KeyStorePrincipalId=".$keyStorePrincipalId[$k];
}
if (!empty($keyStoreSecret[$m])) {
$connectionOptions .= ";KeyStoreSecret=".$keyStoreSecret[$m];
}
// Valid credentials getting skipped
if (($i == 0 and $j == 0 and $k == 0 and $m == 0) or
($i == 0 and $j == 1 and $k == 1 and $m == 1)) {
continue;
}
$connectionOptions .= ";";
try {
// Connect to the AE-enabled database
$conn = new PDO($connectionOptions, $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$columns = array();
$insertQuery = "";
// Generate the INSERT query
formulateSetupQuery($tableName, $dataTypes, $columns, $insertQuery);
createTable($conn, $tableName, $columns);
// Duplicate all values for insertion - one is encrypted, one is not
$testValues = array();
for ($n = 0; $n < sizeof($small_values); ++$n) {
$testValues[] = $small_values[$n];
$testValues[] = $small_values[$n];
}
// Prepare the INSERT query
// This is never expected to fail
$stmt = $conn->prepare($insertQuery);
if ($stmt == false) {
print_r($conn->errorInfo());
fatalError("sqlsrv_prepare failed\n");
}
// Execute the INSERT query
// Failure expected only if the keywords/credentials are wrong
if ($stmt->execute($testValues) == false) {
print_r($stmt->errorInfo());
unset($stmt);
} else {
// The INSERT query succeeded with bad credentials, which
// should only happen when encryption is not enabled.
if (isColEncrypted()) {
fatalError("Successful insertion with bad credentials\n");
}
}
// Free the statement, drop the table, and close the connection
unset($stmt);
dropTable($conn, $tableName);
unset($conn);
} catch (Exception $e) {
$errors = $e->errorInfo;
if (!isColEncrypted()) {
checkErrors(
$errors,
array('CE258', '0'),
array('CE275', '0'),
array('IMSSP', '-85'),
array('IMSSP', '-86'),
array('IMSSP', '-87'),
array('IMSSP', '-88'),
array('08001', '0'),
array('08001', '-1') // SSL error occurs on some Linuxes
);
} else {
checkErrors(
$errors,
array('CE258', '0'),
array('CE275', '0'),
array('IMSSP', '-85'),
array('IMSSP', '-86'),
array('IMSSP', '-87'),
array('IMSSP', '-88'),
array('08001', '0'),
array('08001', '-1'), // SSL error occurs on some Linuxes
array('22018', '206')
);
}
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,42 @@
--TEST--
Test username/password credentials for Azure Key Vault for Always Encrypted.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once('pdo_ae_azure_key_vault_common.php');
// The array of data types corresponding to $small_values in values.php.
// SHORT_STRSIZE is defined in values.php as well.
$dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nvarchar(".SHORT_STRSIZE.")",
"decimal", "float", "real", "bigint", "int", "bit"
);
$connectionOptions = "sqlsrv:Server=$server;Database=$databaseName";
$connectionOptions .= ";ColumnEncryption=enabled";
$connectionOptions .= ";KeyStoreAuthentication=KeyVaultPassword";
$connectionOptions .= ";KeyStorePrincipalId=".$AKVPrincipalName;
$connectionOptions .= ";KeyStoreSecret=".$AKVPassword;
$connectionOptions .= ";";
$tableName = "akv_comparison_table";
// Connect to the AE-enabled database, insert the data, and verify
try {
$conn = new PDO($connectionOptions, $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
insertDataAndVerify($conn, $tableName, $dataTypes, $small_values);
echo "Successful insertion and retrieval with username/password.\n";
unset($conn);
} catch (Exception $e) {
echo "Unexpected error.\n";
print_r($e->errorInfo);
}
?>
--EXPECT--
Successful insertion and retrieval with username/password.

View file

@ -1,17 +1,10 @@
--TEST--
Test for retrieving encrypted data from decimal types columns using PDO::bindColumn
--DESCRIPTION--
Test conversion from decimal types column to output of PDO::PARAM types
With or without ALways Encrypted, conversion works if:
1. From any decimal type column to PDO::PARAM_STR
2. From any decimal type column to PDO::PARAM_LOB
TODO: behavior for teching decimals as PARAM_BOOL and PARAM_INT varies depending on the number being fetched
1. if the number is less than 1, returns 0 (even though the number being fetched is 0.9)
2. if the number is greater than 1 and the number of digits is less than 11, returns the correctly rounded integer (e.g., returns 922 when fetching 922.3)
3. if the number is greater than 1 and the number of digits is greater than 11, returns NULL
need to investigate which should be the correct behavior
for this test, assume to correct behavior is to return NULL
documented in VSO 2730
Test conversion from decimal types to output of PDO::PARAM types
With or without Always Encrypted, conversion should work for all PDO::PARAM
types unless for cases when mapping large decimal / numeric values to
integers (values out of range)
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
@ -27,6 +20,37 @@ $precisions = array(1 => array(0, 1),
$inputValuesInit = array(92233720368547758089223372036854775808, -92233720368547758089223372036854775808);
$inputPrecision = 38;
// function checkNULLs() returns false when at least one of fetched
// values is not null
function checkNULLs($pdoParamType, $typeFull, $det, $rand)
{
if (!is_null($det) || !is_null($rand)) {
echo "Retrieving $typeFull data as $pdoParamType should return NULL\n";
return false;
}
return true;
}
// function compareIntegers() returns false when the fetched values
// are different from the expected inputs
function compareIntegers($pdoParamType, $det, $rand, $inputValues)
{
// Assuming $pdoParamType is PDO::PARAM_BOOL or PDO::PARAM_INT
$input0 = floor($inputValues[0]); // the positive float
$input1 = ceil($inputValues[1]); // the negative float
$matched = ($det == $input0 && $rand == $input1);
if (!$matched) {
echo "****Binding as $pdoParamType failed:****\n";
echo "input 0: "; var_dump($inputValues[0]);
echo "fetched: "; var_dump($det);
echo "input 1: "; var_dump($inputValues[1]);
echo "fetched: "; var_dump($rand);
}
return $matched;
}
try {
$conn = connect("", array(), PDO::ERRMODE_SILENT);
foreach ($dataTypes as $dataType) {
@ -40,7 +64,7 @@ try {
}
// compute the epsilon for comparing doubles
// float in PHP only has a precision of roughtly 14 digits: http://php.net/manual/en/language.types.float.php
// float in PHP only has a precision of roughly 14 digits: http://php.net/manual/en/language.types.float.php
$epsilon;
if ($m1 < 14) {
$epsilon = pow(10, $m2 * -1);
@ -54,9 +78,9 @@ try {
}
$typeFull = "$dataType($m1, $m2)";
echo "\nTesting $typeFull:\n";
// create and populate table containing decimal(m1, m2) or numeric(m1, m2) columns
// create and populate table containing decimal(m1, m2)
// or numeric(m1, m2) columns
$tbname = "test_" . $dataType . $m1 . $m2;
$colMetaArr = array(new ColumnMeta($typeFull, "c_det"), new ColumnMeta($typeFull, "c_rand", null, "randomized"));
createTable($conn, $tbname, $colMetaArr);
@ -68,25 +92,36 @@ try {
$det = "";
$rand = "";
$stmt = $conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('c_det', $det, constant($pdoParamType));
$stmt->bindColumn('c_rand', $rand, constant($pdoParamType));
$row = $stmt->fetch(PDO::FETCH_BOUND);
// check the case when fetching as PDO::PARAM_BOOL, PDO::PARAM_NULL or PDO::PARAM_INT
// with or without AE: should not work
// assume to correct behavior is to return NULL, see description
if ($pdoParamType == "PDO::PARAM_BOOL" || $pdoParamType == "PDO::PARAM_NULL" || $pdoParamType == "PDO::PARAM_INT") {
if (!is_null($det) || !is_null($rand)) {
echo "Retrieving $typeFull data as $pdoParamType should return NULL\n";
// With AE or not, the behavior is the same
$succeeded = false;
if ($pdoParamType == "PDO::PARAM_NULL") {
$succeeded = checkNULLs($pdoParamType, $typeFull, $det, $rand);
} elseif ($pdoParamType == "PDO::PARAM_BOOL" || $pdoParamType == "PDO::PARAM_INT") {
if ($m1 >= 16 && ($m1 != $m2)) {
// When the precision is more than 16 (unless the
// precision = scale), the returned values are
// out of range as integers, so expect NULL
// (the data retrieval should have caused
// an exception but was silenced)
$succeeded = checkNULLs($pdoParamType, $typeFull, $det, $rand);
} else {
$succeeded = compareIntegers($pdoParamType, $det, $rand, $inputValues, $m1, $m2);
}
} else {
if (abs($det - $inputValues[0]) < $epsilon &&
abs($rand - $inputValues[1]) < $epsilon) {
echo "****Retrieving $typeFull as $pdoParamType is supported****\n";
} else {
echo "Retrieving $typeFull as $pdoParamType fails\n";
}
$succeeded = true;
}
}
if (!$succeeded) {
echo "Retrieving $typeFull as $pdoParamType fails\n";
}
}
// cleanup
@ -96,147 +131,11 @@ try {
}
unset($stmt);
unset($conn);
echo "Test successfully done\n";
} catch (PDOException $e) {
echo $e->getMessage();
}
?>
--EXPECT--
Testing decimal(1, 0):
Retrieving decimal(1, 0) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(1, 0) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(1, 0) as PDO::PARAM_STR is supported****
****Retrieving decimal(1, 0) as PDO::PARAM_LOB is supported****
Testing decimal(1, 1):
Retrieving decimal(1, 1) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(1, 1) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(1, 1) as PDO::PARAM_STR is supported****
****Retrieving decimal(1, 1) as PDO::PARAM_LOB is supported****
Testing decimal(4, 0):
Retrieving decimal(4, 0) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(4, 0) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(4, 0) as PDO::PARAM_STR is supported****
****Retrieving decimal(4, 0) as PDO::PARAM_LOB is supported****
Testing decimal(4, 1):
Retrieving decimal(4, 1) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(4, 1) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(4, 1) as PDO::PARAM_STR is supported****
****Retrieving decimal(4, 1) as PDO::PARAM_LOB is supported****
Testing decimal(4, 4):
Retrieving decimal(4, 4) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(4, 4) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(4, 4) as PDO::PARAM_STR is supported****
****Retrieving decimal(4, 4) as PDO::PARAM_LOB is supported****
Testing decimal(16, 0):
****Retrieving decimal(16, 0) as PDO::PARAM_STR is supported****
****Retrieving decimal(16, 0) as PDO::PARAM_LOB is supported****
Testing decimal(16, 1):
****Retrieving decimal(16, 1) as PDO::PARAM_STR is supported****
****Retrieving decimal(16, 1) as PDO::PARAM_LOB is supported****
Testing decimal(16, 4):
****Retrieving decimal(16, 4) as PDO::PARAM_STR is supported****
****Retrieving decimal(16, 4) as PDO::PARAM_LOB is supported****
Testing decimal(16, 16):
Retrieving decimal(16, 16) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(16, 16) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(16, 16) as PDO::PARAM_STR is supported****
****Retrieving decimal(16, 16) as PDO::PARAM_LOB is supported****
Testing decimal(38, 0):
****Retrieving decimal(38, 0) as PDO::PARAM_STR is supported****
****Retrieving decimal(38, 0) as PDO::PARAM_LOB is supported****
Testing decimal(38, 1):
****Retrieving decimal(38, 1) as PDO::PARAM_STR is supported****
****Retrieving decimal(38, 1) as PDO::PARAM_LOB is supported****
Testing decimal(38, 4):
****Retrieving decimal(38, 4) as PDO::PARAM_STR is supported****
****Retrieving decimal(38, 4) as PDO::PARAM_LOB is supported****
Testing decimal(38, 16):
****Retrieving decimal(38, 16) as PDO::PARAM_STR is supported****
****Retrieving decimal(38, 16) as PDO::PARAM_LOB is supported****
Testing decimal(38, 38):
Retrieving decimal(38, 38) data as PDO::PARAM_BOOL should return NULL
Retrieving decimal(38, 38) data as PDO::PARAM_INT should return NULL
****Retrieving decimal(38, 38) as PDO::PARAM_STR is supported****
****Retrieving decimal(38, 38) as PDO::PARAM_LOB is supported****
Testing numeric(1, 0):
Retrieving numeric(1, 0) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(1, 0) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(1, 0) as PDO::PARAM_STR is supported****
****Retrieving numeric(1, 0) as PDO::PARAM_LOB is supported****
Testing numeric(1, 1):
Retrieving numeric(1, 1) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(1, 1) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(1, 1) as PDO::PARAM_STR is supported****
****Retrieving numeric(1, 1) as PDO::PARAM_LOB is supported****
Testing numeric(4, 0):
Retrieving numeric(4, 0) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(4, 0) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(4, 0) as PDO::PARAM_STR is supported****
****Retrieving numeric(4, 0) as PDO::PARAM_LOB is supported****
Testing numeric(4, 1):
Retrieving numeric(4, 1) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(4, 1) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(4, 1) as PDO::PARAM_STR is supported****
****Retrieving numeric(4, 1) as PDO::PARAM_LOB is supported****
Testing numeric(4, 4):
Retrieving numeric(4, 4) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(4, 4) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(4, 4) as PDO::PARAM_STR is supported****
****Retrieving numeric(4, 4) as PDO::PARAM_LOB is supported****
Testing numeric(16, 0):
****Retrieving numeric(16, 0) as PDO::PARAM_STR is supported****
****Retrieving numeric(16, 0) as PDO::PARAM_LOB is supported****
Testing numeric(16, 1):
****Retrieving numeric(16, 1) as PDO::PARAM_STR is supported****
****Retrieving numeric(16, 1) as PDO::PARAM_LOB is supported****
Testing numeric(16, 4):
****Retrieving numeric(16, 4) as PDO::PARAM_STR is supported****
****Retrieving numeric(16, 4) as PDO::PARAM_LOB is supported****
Testing numeric(16, 16):
Retrieving numeric(16, 16) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(16, 16) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(16, 16) as PDO::PARAM_STR is supported****
****Retrieving numeric(16, 16) as PDO::PARAM_LOB is supported****
Testing numeric(38, 0):
****Retrieving numeric(38, 0) as PDO::PARAM_STR is supported****
****Retrieving numeric(38, 0) as PDO::PARAM_LOB is supported****
Testing numeric(38, 1):
****Retrieving numeric(38, 1) as PDO::PARAM_STR is supported****
****Retrieving numeric(38, 1) as PDO::PARAM_LOB is supported****
Testing numeric(38, 4):
****Retrieving numeric(38, 4) as PDO::PARAM_STR is supported****
****Retrieving numeric(38, 4) as PDO::PARAM_LOB is supported****
Testing numeric(38, 16):
****Retrieving numeric(38, 16) as PDO::PARAM_STR is supported****
****Retrieving numeric(38, 16) as PDO::PARAM_LOB is supported****
Testing numeric(38, 38):
Retrieving numeric(38, 38) data as PDO::PARAM_BOOL should return NULL
Retrieving numeric(38, 38) data as PDO::PARAM_INT should return NULL
****Retrieving numeric(38, 38) as PDO::PARAM_STR is supported****
****Retrieving numeric(38, 38) as PDO::PARAM_LOB is supported****
Test successfully done

View file

@ -111,15 +111,7 @@ try {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (abs($row['c_det'] - $inputValues[0]) > $epsilon ||
abs($row['c_rand'] - $inputValues[1]) > $epsilon) {
// TODO: this is a workaround for the test to pass!!!!!
// with AE, doubles cannot be inserted into a decimal(38, 38) column
// remove the following if block to see the bug
// for more information see VSO task 2723
if (isAEConnected() && $m1 == 38 && $m2 == 38) {
echo "****Conversion from $pdoParamType to $typeFull is supported****\n";
} else {
echo "Conversion from $pdoParamType to $typeFull causes data corruption\n";
}
echo "Conversion from $pdoParamType to $typeFull causes data corruption\n";
} else {
echo "****Conversion from $pdoParamType to $typeFull is supported****\n";
}

View file

@ -88,9 +88,19 @@ function testOutputBinary($inout)
$stmt = $conn->prepare($outSql);
trace("\nParam $pdoParamType with INOUT = $inout\n");
if ($inout && $pdoParamType != PDO::PARAM_STR) {
// Currently do not support getting binary as strings + INOUT param
// See VSO 2829 for details
if (!isAEConnected() && $pdoParamType == PDO::PARAM_INT) {
// Without AE, there is no possible way to specify
// binary encoding for this output param type,
// so a value like 'd' will be incorrectly
// interpreted as integer value 100 (its ASCII value).
// Skipping this because this use case is meaningless
// anyway.
// With AE, this output param type would have caused
// an exception
continue;
}
if ($inout) {
$paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT;
} else {
$paramType = $pdoParamType;
@ -101,14 +111,11 @@ function testOutputBinary($inout)
if ($pdoParamType == PDO::PARAM_STR || $pdoParamType == PDO::PARAM_LOB) {
$stmt->bindParam(1, $det, $paramType, $length, PDO::SQLSRV_ENCODING_BINARY);
$stmt->bindParam(2, $rand, $paramType, $length, PDO::SQLSRV_ENCODING_BINARY);
} elseif ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) {
} else {
$det = $rand = 0;
$stmt->bindParam(1, $det, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(2, $rand, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
} else {
$stmt->bindParam(1, $det, $paramType, $length);
$stmt->bindParam(2, $rand, $paramType, $length);
}
}
try {
$stmt->execute();
@ -127,12 +134,8 @@ function testOutputBinary($inout)
printValues($errMsg, $det, $rand, $input0, $input1);
}
} else {
// $pdoParamType is PDO::PARAM_INT
// This only occurs without AE -- likely a rare use case
// With AE enabled, this would have caused an exception
if (strval($det) != $ord0 || strval($rand) != $ord1) {
printValues($errMsg, $det, $rand, $ord0, $ord1);
}
echo "Something went wrong. This should not have happened\n";
printValues($errMsg, $det, $rand, $ord0, $ord1);
}
} catch (PDOException $e) {
$message = $e->getMessage();
@ -147,20 +150,13 @@ function testOutputBinary($inout)
}
} elseif ($pdoParamType == PDO::PARAM_BOOL || PDO::PARAM_INT) {
if (isAEConnected()) {
if ($pdoParamType == PDO::PARAM_INT) {
// Expected to fail with this message
$error = "String data, right truncated for output parameter";
$found = strpos($message, $error);
} else {
// PDO::PARAM_BOOL -
// Expected error 07006 with AE enabled:
// "Restricted data type attribute violation"
// The data value returned for a parameter bound as
// SQL_PARAM_INPUT_OUTPUT or SQL_PARAM_OUTPUT could not
// be converted to the data type identified by the
// ValueType argument in SQLBindParameter.
$found = strpos($message, $errors['07006']);
}
// Expected error 07006 with AE enabled:
// "Restricted data type attribute violation"
// The data value returned for a parameter bound as
// SQL_PARAM_INPUT_OUTPUT or SQL_PARAM_OUTPUT could not
// be converted to the data type identified by the
// ValueType argument in SQLBindParameter.
$found = strpos($message, $errors['07006']);
} else {
// When not AE enabled, expected to fail with something like this message
// "Implicit conversion from data type nvarchar(max) to binary is not allowed. Use the CONVERT function to run this query."
@ -169,6 +165,7 @@ function testOutputBinary($inout)
$found = strpos($message, $error);
}
if ($found === false) {
print "Exception: $message\n";
printValues($errMsg, $det, $rand, $input0, $input1);
}
} else {
@ -207,4 +204,4 @@ echo "Done\n";
unset($conn);
?>
--EXPECT--
Done
Done

View file

@ -120,7 +120,7 @@ function testOutputChars($inout)
if ($found === false) {
printValues($errMsg, $det, $rand, $input0, $input1);
}
} elseif ($pdoParamType == PDO::PARAM_BOOL) {
} elseif ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) {
if (isAEConnected()) {
// Expected error 22003: "Numeric value out of range"
$found = strpos($message, $errors['22003']);

View file

@ -146,7 +146,7 @@ function testOutputDatetimes($inout)
// streams cannot be specified as output parameters."
$found = strpos($message, $errors['IMSSP']);
} elseif (isAEConnected()) {
if ($pdoParamType == PDO::PARAM_BOOL) {
if ($pdoParamType != PDO::PARAM_STR) {
// Expected error 07006: "Restricted data type attribute violation"
// What does this error mean?
// The data value returned for a parameter bound as
@ -159,7 +159,7 @@ function testOutputDatetimes($inout)
$found = strpos($message, $error);
}
} else {
if ($pdoParamType == PDO::PARAM_BOOL) {
if ($pdoParamType != PDO::PARAM_STR) {
$error = "Operand type clash: int is incompatible with $dataType";
} else {
$error = "Error converting data type nvarchar to $dataType";
@ -167,6 +167,7 @@ function testOutputDatetimes($inout)
$found = strpos($message, $error);
}
if ($found === false) {
print $message . PHP_EOL;
printValues($errMsg, $det, $rand, $inputValues);
}
}

View file

@ -50,28 +50,15 @@ function compareFloats($actual, $expected)
function compareIntegers($det, $rand, $inputValues, $pdoParamType)
{
///////////////////////////////////////////////////////////////////////
// See GitHub issue 707 - Fix this method when the problem is addressed
//
// Assume $pdoParamType is PDO::PARAM_BOOL or PDO::PARAM_INT
if (is_string($det)) {
return (!compareFloats(floatval($det), $inputValues[0])
&& !compareFloats(floatval($rand), $inputValues[1]));
} elseif ($pdoParamType == PDO::PARAM_INT) {
$input0 = floor($inputValues[0]); // the positive float
$input1 = ceil($inputValues[1]); // the negative float
return ($det == $input0 && $rand == $input1);
} else {
// $pdoParamType == PDO::PARAM_BOOL
// Expect bool(true) or bool(false) depending on the rounded input values
// But with AE enabled (aforementioned GitHub issue), the fetched values
// are floats instead, which should be fixed
// if $pdoParamType is PDO::PARAM_BOOL, expect bool(true) or bool(false)
// depending on the rounded input values
$input0 = floor($inputValues[0]); // the positive float
$input1 = ceil($inputValues[1]); // the negative float
if (isAEConnected()) {
$det = boolval(floor($det));
$rand = boolval(ceil($rand));
}
return ($det == boolval($input0) && $rand == boolval($input1));
}
@ -146,12 +133,14 @@ function testOutputDecimals($inout)
// call stored procedure
$outSql = getCallProcSqlPlaceholders($spname, 2);
foreach ($pdoParamTypes as $pdoParamType) {
$det = $rand = 0.0;
// Do not initialize $det or $rand as empty strings
// See VSO 2915 for details. The string must be a numeric
// string, and to make it work for all precisions, we
// simply set it to a single-digit string.
$det = $rand = '0';
$stmt = $conn->prepare($outSql);
$len = 2048;
// Do not initialize $det or $rand as empty strings
// See VSO 2915 for details
if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) {
$len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE;
$det = $rand = 0;
@ -195,10 +184,14 @@ function testOutputDecimals($inout)
if ($found === false) {
printValues($errMsg, $det, $rand, $inputValues);
}
} elseif (!isAEConnected() && $precision >= 16 && $pdoParamType == PDO::PARAM_BOOL) {
// When not AE enabled, large numbers are expected to
// fail when converting to booleans
$error = "Error converting data type $dataType to int";
} elseif ($precision >= 16) {
// Large numbers are expected to fail when
// converting to booleans / integers
if (isAEConnected()) {
$error = "Error converting a double (value out of range) to an integer";
} else {
$error = "Error converting data type $dataType to int";
}
$found = strpos($message, $error);
if ($found === false) {
printValues($errMsg, $det, $rand, $inputValues);

View file

@ -85,13 +85,6 @@ function testOutputFloats($fetchNumeric, $inout)
// call stored procedure
$outSql = getCallProcSqlPlaceholders($spname, 2);
foreach ($pdoParamTypes as $pdoParamType) {
if ($pdoParamType == PDO::PARAM_INT && (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN' || substr(PHP_VERSION, 0, 3) == "7.0")) {
// Bug 2876 in VSO: Linux or PHP 7.0 - when retrieving a float as OUTPUT
// or INOUT parameter with PDO::PARAM_INT, the returned values
// are always single digits, regardless of the original floats
continue;
}
$det = 0.0;
$rand = 0.0;
$stmt = $conn->prepare($outSql);

View file

@ -121,7 +121,7 @@ function testOutputNChars($inout)
if ($found === false) {
printValues($errMsg, $det, $rand, $input0, $input1);
}
} elseif ($pdoParamType == PDO::PARAM_BOOL) {
} elseif ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) {
if (isAEConnected()) {
// Expected error 22003: "Numeric value out of range"
$found = strpos($message, $errors['22003']);

View file

@ -26,7 +26,7 @@ insertRow($conn, $tbname, array("c1_bigint" => 922337203685479936));
$outSql = "{CALL $spname (?)}";
$bigintOut = 0;
$stmt = $conn->prepare($outSql);
$stmt->bindParam(1, $bigintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(1, $bigintOut, PDO::PARAM_STR, 32);
$stmt->execute();
printf("Large bigint output:\n" );
var_dump($bigintOut);
@ -35,7 +35,7 @@ printf("\n");
// Call stored procedure with inout
$bigintOut = 0;
$stmt = $conn->prepare($outSql);
$stmt->bindParam(1, $bigintOut, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(1, $bigintOut, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 2048);
$stmt->execute();
printf("Large bigint inout:\n" );
var_dump($bigintOut);

View file

@ -109,11 +109,7 @@ function testEncryptedWithODBC()
$value = "ODBC Driver 13 for SQL Server";
$connectionOptions = "Driver = $value; ColumnEncryption = Enabled;";
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
} else {
$expected = "An invalid keyword 'ColumnEncryption' was specified in the DSN string.";
}
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
connectVerifyOutput($connectionOptions, $expected);
}
@ -122,8 +118,7 @@ function testWrongODBC()
{
global $msodbcsqlMaj;
// TODO: this will change once ODBC 17 is officially released
$value = "ODBC Driver 17 for SQL Server";
$value = "ODBC Driver 11 for SQL Server";
if ($msodbcsqlMaj == 17 || $msodbcsqlMaj < 13) {
$value = "ODBC Driver 13 for SQL Server";
}

View file

@ -21,7 +21,7 @@ if ($c !== false) {
?>
--EXPECTREGEX--
Fatal error: Uncaught PDOException: SQLSTATE\[(28000|08001|HYT00)\]: .*\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\](\[SQL Server\])?(Named Pipes Provider: Could not open a connection to SQL Server \[2\]\. |Login timeout expired|Login failed for user 'sa'\.) in .+(\/|\\)pdo_utf8_conn\.php:[0-9]+
Fatal error: Uncaught PDOException: SQLSTATE\[(28000|08001|HYT00)\]: .*\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\](\[SQL Server\])?(Named Pipes Provider: Could not open a connection to SQL Server \[2\]\. |TCP Provider: Error code (0x2726|0x2AF9)|Login timeout expired|Login failed for user 'sa'\.) in .+(\/|\\)pdo_utf8_conn\.php:[0-9]+
Stack trace:
#0 .+(\/|\\)pdo_utf8_conn\.php\([0-9]+\): PDO->__construct\('sqlsrv:Server=l\.\.\.', 'sa', 'Sunshine4u'\)
#1 {main}

View file

@ -11,10 +11,6 @@ $conn = new PDO($dsn, $uid, $pwd);
if (! $conn) {
echo("Error: could not connect during SKIPIF!");
} elseif (isColEncrypted()) {
if (!(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')) {
die( "Skip, AE test on windows only." );
}
if (!isAEQualified($conn)) {
die("skip - AE feature not supported in the current environment.");
}

File diff suppressed because one or more lines are too long

View file

@ -414,10 +414,16 @@ function printErrors($message = "")
echo $message . "\n";
}
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
if (count($errors) == 0) {
$count = 0;
if (!empty($errors)) {
$count = count($errors);
} else {
$errors = sqlsrv_errors(SQLSRV_ERR_ALL);
if (!empty($errors)) {
$count = count($errors);
}
}
$count = count($errors);
for ($i = 0; $i < $count; $i++) {
echo $errors[$i]['message'] . "\n";
}
@ -427,12 +433,11 @@ function handleErrors()
{
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
$count = 0;
$count = 0;
if (! empty($errors)) {
if (!empty($errors)) {
$count = count($errors);
} else {
$errors = sqlsrv_errors(SQLSRV_ERR_ALL);
if (! empty($errors)) {
if (!empty($errors)) {
$count = count($errors);
}
}

View file

@ -359,6 +359,17 @@ function connect($options = array(), $disableCE = false)
if (isColEncrypted()) {
$connectionOptions = array_merge($connectionOptions, array("ColumnEncryption" => "Enabled"));
}
if ($keystore == 'akv') {
$akv_options = array("KeyStoreAuthentication"=>$AKVKeyStoreAuthentication);
if ($AKVKeyStoreAuthentication == "KeyVaultPassword") {
$akv_options["KeyStorePrincipalId"] = $AKVPrincipalName;
$akv_options["KeyStoreSecret"] = $AKVPassword;
} else if ($AKVKeyStoreAuthentication == "KeyVaultClientSecret") {
$akv_options["KeyStorePrincipalId"] = $AKVClientID;
$akv_options["KeyStoreSecret"] = $AKVSecret;
}
$connectionOptions = array_merge($connectionOptions, $akv_options);
}
}
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {

View file

@ -44,4 +44,11 @@ if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['M
$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv
$dataEncrypted = false; // whether data is to be encrypted
// for Azure Key Vault
$AKVKeyStoreAuthentication = 'TARGET_AKV_AUTH'; // can be KeyVaultPassword or KeyVaultClientSecret
$AKVPrincipalName = 'TARGET_AKV_PRINCIPAL_NAME'; // for use with KeyVaultPassword
$AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPassword
$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret
$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret
?>

View file

@ -10,10 +10,6 @@ $conn = AE\connect();
if (! $conn) {
echo("Error: could not connect during SKIPIF!");
} elseif (AE\isColEncrypted()) {
if (!isWindows()) {
die( "Skip, AE test on windows only." );
}
if (!AE\isQualified($conn)) {
die("skip - AE feature not supported in the current environment.");
}

View file

@ -0,0 +1,45 @@
--TEST--
Test client ID/secret credentials for Azure Key Vault for Always Encrypted.
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('sqlsrv_ae_azure_key_vault_common.php');
// The array of data types corresponding to $small_values in values.php.
// SHORT_STRSIZE is defined in values.php as well.
$dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nvarchar(".SHORT_STRSIZE.")",
"decimal", "float", "real", "bigint", "int", "bit"
);
// Test data insertion and retrieval with username/password
// and client Id/client secret combinations.
$connectionOptions = array("CharacterSet"=>"UTF-8",
"database"=>$databaseName,
"uid"=>$uid,
"pwd"=>$pwd,
"ConnectionPooling"=>0);
$connectionOptions['ColumnEncryption'] = "enabled";
$connectionOptions['KeyStoreAuthentication'] = "KeyVaultClientSecret";
$connectionOptions['KeyStorePrincipalId'] = $AKVClientID;
$connectionOptions['KeyStoreSecret'] = $AKVSecret;
$tableName = "akv_comparison_table";
// Connect to the AE-enabled database, insert the data, and verify
$conn = sqlsrv_connect($server, $connectionOptions);
if (!$conn) {
$errors = sqlsrv_errors();
fatalError("Connection failed while testing good credentials.\n");
} else {
insertDataAndVerify($conn, $tableName, $dataTypes, $small_values);
echo "Successful insertion and retrieval with client ID/secret.\n";
sqlsrv_close($conn);
}
?>
--EXPECT--
Successful insertion and retrieval with client ID/secret.

View file

@ -0,0 +1,92 @@
<?php
require_once('MsCommon.inc');
require_once('values.php');
// Set up the columns and build the insert query. Each data type has an
// AE-encrypted and a non-encrypted column side by side in the table.
// If column encryption is not set in MsSetup.inc, this function simply
// creates two non-encrypted columns side-by-side for each type.
function formulateSetupQuery($tableName, &$dataTypes, &$columns, &$insertQuery)
{
$columns = array();
$queryTypes = "(";
$valuesString = "VALUES (";
$numTypes = sizeof($dataTypes);
for ($i = 0; $i < $numTypes; ++$i) {
// Replace parentheses for column names
$colname = str_replace(array("(", ",", ")"), array("_", "_", ""), $dataTypes[$i]);
$columns[] = new AE\ColumnMeta($dataTypes[$i], "c_".$colname."_AE");
$columns[] = new AE\ColumnMeta($dataTypes[$i], "c_".$colname, null, true, true);
$queryTypes .= "c_"."$colname, ";
$queryTypes .= "c_"."$colname"."_AE, ";
$valuesString .= "?, ?, ";
}
$queryTypes = substr($queryTypes, 0, -2).")";
$valuesString = substr($valuesString, 0, -2).")";
$insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString;
}
// Create the table and insert the data, then retrieve it back and make
// sure the encrypted and non-encrypted values are identical.
function insertDataAndVerify($conn, $tableName, $dataTypes, $values)
{
$columns = array();
$insertQuery = "";
// Generate the INSERT query
formulateSetupQuery($tableName, $dataTypes, $columns, $insertQuery);
$stmt = AE\createTable($conn, $tableName, $columns);
if (!$stmt) {
fatalError("Failed to create table $tableName\n");
}
// Duplicate all values for insertion - one is encrypted, one is not
$testValues = array();
for ($n = 0; $n < sizeof($values); ++$n) {
$testValues[] = $values[$n];
$testValues[] = $values[$n];
}
// Prepare the INSERT query
// This is never expected to fail
$stmt = sqlsrv_prepare($conn, $insertQuery, $testValues);
if ($stmt == false) {
print_r(sqlsrv_errors());
fatalError("sqlsrv_prepare failed\n");
}
// Execute the INSERT query
// This should not fail since our credentials are correct
if (sqlsrv_execute($stmt) == false) {
$errors = sqlsrv_errors();
fatalError("INSERT query execution failed with good credentials.\n");
} else {
// Get the data back and compare encrypted and non-encrypted versions
$selectQuery = "SELECT * FROM $tableName";
$stmt1 = sqlsrv_query($conn, $selectQuery);
$data = sqlsrv_fetch_array($stmt1, SQLSRV_FETCH_NUMERIC);
if (sizeof($data) != 2*sizeof($dataTypes)) {
fatalError("Incorrect number of fields returned.\n");
}
for ($n = 0; $n < sizeof($data); $n += 2) {
if ($data[$n] != $data[$n + 1]) {
echo "Failed on field $n: ".$data[$n]." ".$data[$n + 1]."\n";
fatalError("AE and non-AE values do not match.\n");
}
}
sqlsrv_free_stmt($stmt);
sqlsrv_free_stmt($stmt1);
}
// Drop the table
dropTable($conn, $tableName);
}
?>

View file

@ -0,0 +1,168 @@
--TEST--
Test connection keywords for Azure Key Vault for Always Encrypted.
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('sqlsrv_ae_azure_key_vault_common.php');
// This test only applies to Azure Key Vault, or to no encryption at all
if ($keystore != 'none' and $keystore != 'akv') {
echo "Done.\n";
exit();
}
// We will test the direct product (set of all possible combinations) of the following
$columnEncryption = ['enabled', 'disabled', 'notvalid', ''];
$keyStoreAuthentication = ['KeyVaultPassword', 'KeyVaultClientSecret', 'KeyVaultNothing', ''];
$keyStorePrincipalId = [$AKVPrincipalName, $AKVClientID, 'notaname', ''];
$keyStoreSecret = [$AKVPassword, $AKVSecret, 'notasecret', ''];
function checkErrors($errors, ...$codes)
{
$codeFound = false;
foreach ($codes as $code) {
if ($code[0]==$errors[0][0] and $code[1]==$errors[0][1]) {
$codeFound = true;
}
}
if ($codeFound == false) {
echo "Error: ";
print_r($errors);
echo "\nExpected: ";
print_r($codes);
echo "\n";
fatalError("Error code not found.\n");
}
}
// The array of data types corresponding to $small_values in values.php.
// SHORT_STRSIZE is defined in values.php as well.
$dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nvarchar(".SHORT_STRSIZE.")",
"decimal", "float", "real", "bigint", "int", "bit"
);
$tableName = "akv_comparison_table";
// 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
// we can't run a PHP test without caching, and if we started with good
// credentials then subsequent calls with bad credentials can work, which
// would muddle the results of this test. Good credentials are tested in a
// separate test.
for ($i = 0; $i < sizeof($columnEncryption); ++$i) {
for ($j = 0; $j < sizeof($keyStoreAuthentication); ++$j) {
for ($k = 0; $k < sizeof($keyStorePrincipalId); ++$k) {
for ($m = 0; $m < sizeof($keyStoreSecret); ++$m) {
$connectionOptions = array("CharacterSet"=>"UTF-8",
"database"=>$databaseName,
"uid"=>$uid,
"pwd"=>$pwd,
"ConnectionPooling"=>0);
if (!empty($columnEncryption[$i])) {
$connectionOptions['ColumnEncryption'] = $columnEncryption[$i];
}
if (!empty($keyStoreAuthentication[$j])) {
$connectionOptions['KeyStoreAuthentication'] = $keyStoreAuthentication[$j];
}
if (!empty($keyStorePrincipalId[$k])) {
$connectionOptions['KeyStorePrincipalId'] = $keyStorePrincipalId[$k];
}
if (!empty($keyStoreSecret[$m])) {
$connectionOptions['KeyStoreSecret'] = $keyStoreSecret[$m];
}
// Valid credentials getting skipped
if (($i == 0 and $j == 0 and $k == 0 and $m == 0) or
($i == 0 and $j == 1 and $k == 1 and $m == 1)) {
continue;
}
// Connect to the AE-enabled database
// Failure is expected when the keyword combination is wrong
$conn = sqlsrv_connect($server, $connectionOptions);
if (!$conn) {
$errors = sqlsrv_errors();
checkErrors(
$errors,
array('08001','0'),
array('08001','-1'), // SSL error on some Linuxes
array('IMSSP','-110'),
array('IMSSP','-111'),
array('IMSSP','-112'),
array('IMSSP','-113')
);
} else {
$columns = array();
$insertQuery = "";
// Generate the INSERT query
formulateSetupQuery($tableName, $dataTypes, $columns, $insertQuery);
$stmt = AE\createTable($conn, $tableName, $columns);
if (!$stmt) {
fatalError("Failed to create table $tableName.\n");
}
// Duplicate all values for insertion - one is encrypted, one is not
$testValues = array();
for ($n = 0; $n < sizeof($small_values); ++$n) {
$testValues[] = $small_values[$n];
$testValues[] = $small_values[$n];
}
// Prepare the INSERT query
// This is never expected to fail
$stmt = sqlsrv_prepare($conn, $insertQuery, $testValues);
if ($stmt == false) {
print_r(sqlsrv_errors());
fatalError("sqlsrv_prepare failed.\n");
}
// Execute the INSERT query
// This is where we expect failure if the credentials are incorrect
if (sqlsrv_execute($stmt) == false) {
$errors = sqlsrv_errors();
if (!AE\isDataEncrypted()) {
checkErrors(
$errors,
array('CE258', '0'),
array('CE275', '0')
);
} else {
checkErrors(
$errors,
array('CE258', '0'),
array('CE275', '0'),
array('22018', '206')
);
}
sqlsrv_free_stmt($stmt);
} else {
// The INSERT query succeeded with bad credentials, which
// should only happen when encryption is not enabled.
if (AE\isDataEncrypted()) {
fatalError("Successful insertion with bad credentials\n");
}
}
// Drop the table and close the connection
dropTable($conn, $tableName);
sqlsrv_close($conn);
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,45 @@
--TEST--
Test username/password credentials for Azure Key Vault for Always Encrypted.
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('sqlsrv_ae_azure_key_vault_common.php');
// The array of data types corresponding to $small_values in values.php.
// SHORT_STRSIZE is defined in values.php as well.
$dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nvarchar(".SHORT_STRSIZE.")",
"decimal", "float", "real", "bigint", "int", "bit"
);
// Test data insertion and retrieval with username/password
// and client Id/client secret combinations.
$connectionOptions = array("CharacterSet"=>"UTF-8",
"database"=>$databaseName,
"uid"=>$uid,
"pwd"=>$pwd,
"ConnectionPooling"=>0);
$connectionOptions['ColumnEncryption'] = "enabled";
$connectionOptions['KeyStoreAuthentication'] = "KeyVaultPassword";
$connectionOptions['KeyStorePrincipalId'] = $AKVPrincipalName;
$connectionOptions['KeyStoreSecret'] = $AKVPassword;
$tableName = "akv_comparison_table";
// Connect to the AE-enabled database, insert the data, and verify
$conn = sqlsrv_connect($server, $connectionOptions);
if (!$conn) {
$errors = sqlsrv_errors();
fatalError("Connection failed while testing good credentials.\n");
} else {
insertDataAndVerify($conn, $tableName, $dataTypes, $small_values);
echo "Successful insertion and retrieval with username/password.\n";
sqlsrv_close($conn);
}
?>
--EXPECT--
Successful insertion and retrieval with username/password.

View file

@ -12,11 +12,11 @@ Without AlwaysEncrypted, implicit conversion between precisions or scales works
require_once('MsCommon.inc');
$dataTypes = array("decimal", "numeric");
$precisions = array(/*1 => array(0, 1),
$precisions = array(1 => array(0, 1),
4 => array(0, 1, 4),
16 => array(0, 1, 4, 16),*/
19 => array(/*0,*/ 1, 4, 16, 19),
38 => array(/*0,*/ 1, 4, 16, 37/*,38*/));
16 => array(0, 1, 4, 16),
19 => array(0, 1, 4, 16, 19),
38 => array(0, 1, 4, 16, 38));
$sqlTypes = array("SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC");
$sqltypePrecisions = $precisions;
$inputValuesInit = array(92233720368547758089223372036854775808, -92233720368547758089223372036854775808);
@ -136,6 +136,86 @@ foreach ($dataTypes as $dataType) {
sqlsrv_close($conn);
?>
--EXPECT--
Testing decimal(1, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(1, 0) to output SQLSRV_SQLTYPE_DECIMAL(1, 0) is supported****
****Conversion from decimal(1, 0) to output SQLSRV_SQLTYPE_NUMERIC(1, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(1, 0) to output SQLSRV_SQLTYPE_DECIMAL(1, 0) is supported****
****Conversion from decimal(1, 0) to output SQLSRV_SQLTYPE_NUMERIC(1, 0) is supported****
Testing decimal(1, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(1, 1) to output SQLSRV_SQLTYPE_DECIMAL(1, 1) is supported****
****Conversion from decimal(1, 1) to output SQLSRV_SQLTYPE_NUMERIC(1, 1) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(1, 1) to output SQLSRV_SQLTYPE_DECIMAL(1, 1) is supported****
****Conversion from decimal(1, 1) to output SQLSRV_SQLTYPE_NUMERIC(1, 1) is supported****
Testing decimal(4, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(4, 0) to output SQLSRV_SQLTYPE_DECIMAL(4, 0) is supported****
****Conversion from decimal(4, 0) to output SQLSRV_SQLTYPE_NUMERIC(4, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(4, 0) to output SQLSRV_SQLTYPE_DECIMAL(4, 0) is supported****
****Conversion from decimal(4, 0) to output SQLSRV_SQLTYPE_NUMERIC(4, 0) is supported****
Testing decimal(4, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(4, 1) to output SQLSRV_SQLTYPE_DECIMAL(4, 1) is supported****
****Conversion from decimal(4, 1) to output SQLSRV_SQLTYPE_NUMERIC(4, 1) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(4, 1) to output SQLSRV_SQLTYPE_DECIMAL(4, 1) is supported****
****Conversion from decimal(4, 1) to output SQLSRV_SQLTYPE_NUMERIC(4, 1) is supported****
Testing decimal(4, 4):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(4, 4) to output SQLSRV_SQLTYPE_DECIMAL(4, 4) is supported****
****Conversion from decimal(4, 4) to output SQLSRV_SQLTYPE_NUMERIC(4, 4) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(4, 4) to output SQLSRV_SQLTYPE_DECIMAL(4, 4) is supported****
****Conversion from decimal(4, 4) to output SQLSRV_SQLTYPE_NUMERIC(4, 4) is supported****
Testing decimal(16, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(16, 0) to output SQLSRV_SQLTYPE_DECIMAL(16, 0) is supported****
****Conversion from decimal(16, 0) to output SQLSRV_SQLTYPE_NUMERIC(16, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(16, 0) to output SQLSRV_SQLTYPE_DECIMAL(16, 0) is supported****
****Conversion from decimal(16, 0) to output SQLSRV_SQLTYPE_NUMERIC(16, 0) is supported****
Testing decimal(16, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(16, 1) to output SQLSRV_SQLTYPE_DECIMAL(16, 1) is supported****
****Conversion from decimal(16, 1) to output SQLSRV_SQLTYPE_NUMERIC(16, 1) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(16, 1) to output SQLSRV_SQLTYPE_DECIMAL(16, 1) is supported****
****Conversion from decimal(16, 1) to output SQLSRV_SQLTYPE_NUMERIC(16, 1) is supported****
Testing decimal(16, 4):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(16, 4) to output SQLSRV_SQLTYPE_DECIMAL(16, 4) is supported****
****Conversion from decimal(16, 4) to output SQLSRV_SQLTYPE_NUMERIC(16, 4) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(16, 4) to output SQLSRV_SQLTYPE_DECIMAL(16, 4) is supported****
****Conversion from decimal(16, 4) to output SQLSRV_SQLTYPE_NUMERIC(16, 4) is supported****
Testing decimal(16, 16):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(16, 16) to output SQLSRV_SQLTYPE_DECIMAL(16, 16) is supported****
****Conversion from decimal(16, 16) to output SQLSRV_SQLTYPE_NUMERIC(16, 16) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(16, 16) to output SQLSRV_SQLTYPE_DECIMAL(16, 16) is supported****
****Conversion from decimal(16, 16) to output SQLSRV_SQLTYPE_NUMERIC(16, 16) is supported****
Testing decimal(19, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(19, 0) to output SQLSRV_SQLTYPE_DECIMAL(19, 0) is supported****
****Conversion from decimal(19, 0) to output SQLSRV_SQLTYPE_NUMERIC(19, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(19, 0) to output SQLSRV_SQLTYPE_DECIMAL(19, 0) is supported****
****Conversion from decimal(19, 0) to output SQLSRV_SQLTYPE_NUMERIC(19, 0) is supported****
Testing decimal(19, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(19, 1) to output SQLSRV_SQLTYPE_DECIMAL(19, 1) is supported****
@ -168,6 +248,14 @@ Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(19, 19) to output SQLSRV_SQLTYPE_DECIMAL(19, 19) is supported****
****Conversion from decimal(19, 19) to output SQLSRV_SQLTYPE_NUMERIC(19, 19) is supported****
Testing decimal(38, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(38, 0) to output SQLSRV_SQLTYPE_DECIMAL(38, 0) is supported****
****Conversion from decimal(38, 0) to output SQLSRV_SQLTYPE_NUMERIC(38, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(38, 0) to output SQLSRV_SQLTYPE_DECIMAL(38, 0) is supported****
****Conversion from decimal(38, 0) to output SQLSRV_SQLTYPE_NUMERIC(38, 0) is supported****
Testing decimal(38, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(38, 1) to output SQLSRV_SQLTYPE_DECIMAL(38, 1) is supported****
@ -192,13 +280,93 @@ Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(38, 16) to output SQLSRV_SQLTYPE_DECIMAL(38, 16) is supported****
****Conversion from decimal(38, 16) to output SQLSRV_SQLTYPE_NUMERIC(38, 16) is supported****
Testing decimal(38, 37):
Testing decimal(38, 38):
Testing as SQLSRV_PARAM_OUT:
****Conversion from decimal(38, 37) to output SQLSRV_SQLTYPE_DECIMAL(38, 37) is supported****
****Conversion from decimal(38, 37) to output SQLSRV_SQLTYPE_NUMERIC(38, 37) is supported****
****Conversion from decimal(38, 38) to output SQLSRV_SQLTYPE_DECIMAL(38, 38) is supported****
****Conversion from decimal(38, 38) to output SQLSRV_SQLTYPE_NUMERIC(38, 38) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from decimal(38, 37) to output SQLSRV_SQLTYPE_DECIMAL(38, 37) is supported****
****Conversion from decimal(38, 37) to output SQLSRV_SQLTYPE_NUMERIC(38, 37) is supported****
****Conversion from decimal(38, 38) to output SQLSRV_SQLTYPE_DECIMAL(38, 38) is supported****
****Conversion from decimal(38, 38) to output SQLSRV_SQLTYPE_NUMERIC(38, 38) is supported****
Testing numeric(1, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(1, 0) to output SQLSRV_SQLTYPE_DECIMAL(1, 0) is supported****
****Conversion from numeric(1, 0) to output SQLSRV_SQLTYPE_NUMERIC(1, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(1, 0) to output SQLSRV_SQLTYPE_DECIMAL(1, 0) is supported****
****Conversion from numeric(1, 0) to output SQLSRV_SQLTYPE_NUMERIC(1, 0) is supported****
Testing numeric(1, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(1, 1) to output SQLSRV_SQLTYPE_DECIMAL(1, 1) is supported****
****Conversion from numeric(1, 1) to output SQLSRV_SQLTYPE_NUMERIC(1, 1) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(1, 1) to output SQLSRV_SQLTYPE_DECIMAL(1, 1) is supported****
****Conversion from numeric(1, 1) to output SQLSRV_SQLTYPE_NUMERIC(1, 1) is supported****
Testing numeric(4, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(4, 0) to output SQLSRV_SQLTYPE_DECIMAL(4, 0) is supported****
****Conversion from numeric(4, 0) to output SQLSRV_SQLTYPE_NUMERIC(4, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(4, 0) to output SQLSRV_SQLTYPE_DECIMAL(4, 0) is supported****
****Conversion from numeric(4, 0) to output SQLSRV_SQLTYPE_NUMERIC(4, 0) is supported****
Testing numeric(4, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(4, 1) to output SQLSRV_SQLTYPE_DECIMAL(4, 1) is supported****
****Conversion from numeric(4, 1) to output SQLSRV_SQLTYPE_NUMERIC(4, 1) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(4, 1) to output SQLSRV_SQLTYPE_DECIMAL(4, 1) is supported****
****Conversion from numeric(4, 1) to output SQLSRV_SQLTYPE_NUMERIC(4, 1) is supported****
Testing numeric(4, 4):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(4, 4) to output SQLSRV_SQLTYPE_DECIMAL(4, 4) is supported****
****Conversion from numeric(4, 4) to output SQLSRV_SQLTYPE_NUMERIC(4, 4) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(4, 4) to output SQLSRV_SQLTYPE_DECIMAL(4, 4) is supported****
****Conversion from numeric(4, 4) to output SQLSRV_SQLTYPE_NUMERIC(4, 4) is supported****
Testing numeric(16, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(16, 0) to output SQLSRV_SQLTYPE_DECIMAL(16, 0) is supported****
****Conversion from numeric(16, 0) to output SQLSRV_SQLTYPE_NUMERIC(16, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(16, 0) to output SQLSRV_SQLTYPE_DECIMAL(16, 0) is supported****
****Conversion from numeric(16, 0) to output SQLSRV_SQLTYPE_NUMERIC(16, 0) is supported****
Testing numeric(16, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(16, 1) to output SQLSRV_SQLTYPE_DECIMAL(16, 1) is supported****
****Conversion from numeric(16, 1) to output SQLSRV_SQLTYPE_NUMERIC(16, 1) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(16, 1) to output SQLSRV_SQLTYPE_DECIMAL(16, 1) is supported****
****Conversion from numeric(16, 1) to output SQLSRV_SQLTYPE_NUMERIC(16, 1) is supported****
Testing numeric(16, 4):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(16, 4) to output SQLSRV_SQLTYPE_DECIMAL(16, 4) is supported****
****Conversion from numeric(16, 4) to output SQLSRV_SQLTYPE_NUMERIC(16, 4) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(16, 4) to output SQLSRV_SQLTYPE_DECIMAL(16, 4) is supported****
****Conversion from numeric(16, 4) to output SQLSRV_SQLTYPE_NUMERIC(16, 4) is supported****
Testing numeric(16, 16):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(16, 16) to output SQLSRV_SQLTYPE_DECIMAL(16, 16) is supported****
****Conversion from numeric(16, 16) to output SQLSRV_SQLTYPE_NUMERIC(16, 16) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(16, 16) to output SQLSRV_SQLTYPE_DECIMAL(16, 16) is supported****
****Conversion from numeric(16, 16) to output SQLSRV_SQLTYPE_NUMERIC(16, 16) is supported****
Testing numeric(19, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(19, 0) to output SQLSRV_SQLTYPE_DECIMAL(19, 0) is supported****
****Conversion from numeric(19, 0) to output SQLSRV_SQLTYPE_NUMERIC(19, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(19, 0) to output SQLSRV_SQLTYPE_DECIMAL(19, 0) is supported****
****Conversion from numeric(19, 0) to output SQLSRV_SQLTYPE_NUMERIC(19, 0) is supported****
Testing numeric(19, 1):
Testing as SQLSRV_PARAM_OUT:
@ -232,6 +400,14 @@ Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(19, 19) to output SQLSRV_SQLTYPE_DECIMAL(19, 19) is supported****
****Conversion from numeric(19, 19) to output SQLSRV_SQLTYPE_NUMERIC(19, 19) is supported****
Testing numeric(38, 0):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(38, 0) to output SQLSRV_SQLTYPE_DECIMAL(38, 0) is supported****
****Conversion from numeric(38, 0) to output SQLSRV_SQLTYPE_NUMERIC(38, 0) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(38, 0) to output SQLSRV_SQLTYPE_DECIMAL(38, 0) is supported****
****Conversion from numeric(38, 0) to output SQLSRV_SQLTYPE_NUMERIC(38, 0) is supported****
Testing numeric(38, 1):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(38, 1) to output SQLSRV_SQLTYPE_DECIMAL(38, 1) is supported****
@ -256,10 +432,10 @@ Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(38, 16) to output SQLSRV_SQLTYPE_DECIMAL(38, 16) is supported****
****Conversion from numeric(38, 16) to output SQLSRV_SQLTYPE_NUMERIC(38, 16) is supported****
Testing numeric(38, 37):
Testing numeric(38, 38):
Testing as SQLSRV_PARAM_OUT:
****Conversion from numeric(38, 37) to output SQLSRV_SQLTYPE_DECIMAL(38, 37) is supported****
****Conversion from numeric(38, 37) to output SQLSRV_SQLTYPE_NUMERIC(38, 37) is supported****
****Conversion from numeric(38, 38) to output SQLSRV_SQLTYPE_DECIMAL(38, 38) is supported****
****Conversion from numeric(38, 38) to output SQLSRV_SQLTYPE_NUMERIC(38, 38) is supported****
Testing as SQLSRV_PARAM_INOUT:
****Conversion from numeric(38, 37) to output SQLSRV_SQLTYPE_DECIMAL(38, 37) is supported****
****Conversion from numeric(38, 37) to output SQLSRV_SQLTYPE_NUMERIC(38, 37) is supported****
****Conversion from numeric(38, 38) to output SQLSRV_SQLTYPE_DECIMAL(38, 38) is supported****
****Conversion from numeric(38, 38) to output SQLSRV_SQLTYPE_NUMERIC(38, 38) is supported****

View file

@ -0,0 +1,305 @@
--TEST--
Test fetching data by conversion with CAST in the SELECT statement
--DESCRIPTION--
This test checks the allowed data type conversions in SELECT statements under Always Encrypted and non-encrypted
Reference chart for conversions found at https://www.microsoft.com/en-us/download/details.aspx?id=35834
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('sqlsrv_ae_azure_key_vault_common.php');
// These are the errors we expect to see if a conversion fails.
// 22001 String data is right-truncated
// 22003 Numeric value out of range/Overflow converting to numeric type
// 22007 Conversion (date/time from string) failed
// 22018 Conversion not allowed
// 42S22 Column not found
// 6522 .NET Framework error in hierarchyId construction
// 8114 Error converting binary/string type to numeric type
// 8169 Error converting from string to uniqueID
function checkErrors(&$convError)
{
if ($convError[0][0] != '22018' and
$convError[0][0] != '22001' and
$convError[0][0] != '22003' and
$convError[0][0] != '22007' and
$convError[0][0] != '42S22' and
$convError[0][1] != '6522' and
$convError[0][1] != '8114' and
$convError[0][1] != '8169') {
print_r($convError);
fatalError("Conversion failed with unexpected error message. i=$i, j=$j, v=$v\n");
}
}
// Build the select queries. We want every combination of types for conversion
// testing, so the matrix of queries selects every type from every column
// and convert using CAST.
function formulateSelectQuery($tableName, &$selectQuery, &$selectQueryAE, &$dataTypes)
{
$numTypes = sizeof($dataTypes);
for ($i = 0; $i < $numTypes; ++$i) {
$selectQuery[] = array();
// Replace parentheses for column names
// The column names look like c_binary_256_AE and c_binary_256
$colnamei = str_replace(array("(", ",", ")"), array("_", "_", ""), $dataTypes[$i]);
for ($j = 0; $j < sizeof($dataTypes); ++$j) {
$selectQuery[$i][] = "SELECT CAST(c_".$colnamei." AS $dataTypes[$j]) FROM $tableName";
$selectQueryAE[$i][] = "SELECT CAST(c_".$colnamei."_AE AS $dataTypes[$j]) FROM $tableName";
}
}
}
$dataTypes = array ("binary(".STRSIZE.")", "varbinary(".STRSIZE.")", "varbinary(max)", "char(".STRSIZE.")",
"varchar(".STRSIZE.")", "varchar(max)", "nchar(".STRSIZE.")", "nvarchar(".STRSIZE.")",
"nvarchar(max)", "datetime", "smalldatetime", "date", "time(5)", "datetimeoffset(5)",
"datetime2(5)", "decimal(28,4)", "numeric(32,4)", "float", "real", "bigint", "int",
"smallint", "tinyint", "bit",
"binary(".LONG_STRSIZE.")", "varbinary(".LONG_STRSIZE.")", "char(".LONG_STRSIZE.")",
"varchar(".LONG_STRSIZE.")", "nchar(".LONG_STRSIZE.")", "nvarchar(".LONG_STRSIZE.")",
"time", "datetimeoffset", "datetime2", "decimal(32,4)", "numeric(36,4)"
);
// Conversion matrix for SQL types listing allowed conversions for
// non-encrypted data, based on the reference conversion chart
// at https://www.microsoft.com/en-us/download/details.aspx?id=35834
// i = implicit conversion
// e = explicit conversion
// x = conversion not allowed
// @ = not applicable
// c = explicit CAST required
$conversionMatrix = array(array('@','i','i','i','i','i','i','i','i','i','i','e','e','e','e','i','i','x','x','i','i','i','i','i','i','i','i','i','i','i','e','e','e','i','i'),//binary
array('i','@','i','i','i','i','i','i','i','i','i','e','e','e','e','i','i','x','x','i','i','i','i','i','i','i','i','i','i','i','e','e','e','i','i'),//varbinary
array('i','i','@','i','i','i','i','i','i','i','i','e','e','e','e','i','i','x','x','i','i','i','i','i','i','i','i','i','i','i','e','e','e','i','i'),//varbinary(max)
array('e','e','e','@','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','i','i','i','i','i','i'),//char
array('e','e','e','i','@','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','i','i','i','i','i','i'),//varchar
array('e','e','e','i','i','@','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','i','i','i','i','i','i'),//varchar(max)
array('e','e','e','i','i','i','@','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','i','i','i','i','i','i'),//nchar
array('e','e','e','i','i','i','i','@','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','i','i','i','i','i','i'),//nvarchar
array('e','e','e','i','i','i','i','i','@','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','i','i','i','i','i','i'),//nvarchar(max)
array('e','e','e','i','i','i','i','i','i','@','i','i','i','i','i','e','e','e','e','e','e','e','e','e','e','e','i','i','i','i','i','i','i','e','e'),//datetime
array('e','e','e','i','i','i','i','i','i','i','@','i','i','i','i','e','e','e','e','e','e','e','e','e','e','e','i','i','i','i','i','i','i','e','e'),//samlldatetime
array('e','e','e','i','i','i','i','i','i','i','i','@','x','i','i','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','x','i','i','x','x'),//date
array('e','e','e','i','i','i','i','i','i','i','i','x','@','i','i','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','i','i','i','x','x'),//time
array('e','e','e','i','i','i','i','i','i','i','i','i','i','@','i','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','i','i','i','x','x'),//datetimeoffset
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','@','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','i','i','i','x','x'),//datetime2
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','c','c','i','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','c','c'),//decimal
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','c','c','i','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','c','c'),//numeric
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','c','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','i','i'),//float
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','i','@','i','i','i','i','i','i','i','i','i','i','i','x','x','x','i','i'),//real
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','i','i','@','i','i','i','i','i','i','i','i','i','i','x','x','x','i','i'),//bigint
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','i','i','i','@','i','i','i','i','i','i','i','i','i','x','x','x','i','i'),//int
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','i','i','i','i','@','i','i','i','i','i','i','i','i','x','x','x','i','i'),//smallint
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','i','i','i','i','i','@','i','i','i','i','i','i','i','x','x','x','i','i'),//tinyint
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','i','i','i','i','i','i','i','i','@','i','i','i','i','i','i','x','x','x','i','i'),//bit
array('i','i','i','i','i','i','i','i','i','i','i','e','e','e','e','i','i','x','x','i','i','i','i','i','@','i','i','i','i','i','e','e','e','i','i'),//binary
array('i','i','i','i','i','i','i','i','i','i','i','e','e','e','e','i','i','x','x','i','i','i','i','i','i','@','i','i','i','i','e','e','e','i','i'),//varbinary
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','@','i','i','i','i','i','i','i','i'),//char
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','@','i','i','i','i','i','i','i'),//varchar
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','@','i','i','i','i','i','i'),//nchar
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','i','e','e','i','i','i','@','i','i','i','i','i'),//nvarchar
array('e','e','e','i','i','i','i','i','i','i','i','x','i','i','i','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','@','i','i','x','x'),//time
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','i','@','i','x','x'),//datetimeoffset
array('e','e','e','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','x','x','x','x','x','e','e','i','i','i','i','i','i','@','x','x'),//datetime2
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','c','c','i','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','c','c'),//decimal
array('i','i','i','i','i','i','i','i','i','i','i','x','x','x','x','c','c','i','i','i','i','i','i','i','i','i','i','i','i','i','x','x','x','c','c'),//numeric
);
// The conversion matrix for AE is more restrictive
// y = allowed conversion
// x = not allowed
$conversionMatrixAE = array(array('y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x','x','x'),//binary
array('y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x','x','x'),//varbinary
array('x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//varbinary(max)
array('x','x','x','y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x'),//char
array('x','x','x','y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x'),//varchar
array('x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//varchar(max)
array('x','x','x','x','x','x','y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x'),//nchar
array('x','x','x','x','x','x','y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x'),//nvarchar
array('x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//nvarchar(max)
array('x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//datetime
array('x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//samlldatetime
array('x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//date
array('x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x'),//time
array('x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x'),//datetimeoffset
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x'),//datetime2
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//decimal
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x'),//numeric
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//float
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//real
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//bigint
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x'),//int
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','y','x','x','x','x','x','x','x','x','x','x','x','x','x'),//smallint
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','y','y','x','x','x','x','x','x','x','x','x','x','x','x'),//tinyint
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','y','y','y','x','x','x','x','x','x','x','x','x','x','x'),//bit
array('x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x','x','x'),//binary
array('x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x','x','x'),//varbinary
array('x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x'),//char
array('x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x','x','x'),//varchar
array('x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x'),//nchar
array('x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','y','x','x','x','x','x'),//nvarchar
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x'),//time
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x'),//datetimeoffset
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x'),//datetime2
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y','x'),//decimal
array('x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','y'),//numeric
);
set_time_limit(0);
sqlsrv_configure('WarningsReturnAsErrors', 1);
$connectionInfo = array("CharacterSet"=>"UTF-8");
$conn = AE\connect($connectionInfo);
if (!$conn) {
fatalError("Could not connect.\n");
}
$tableName = "type_conversion_table";
$columns = array();
$insertQuery = "";
formulateSetupQuery($tableName, $dataTypes, $columns, $insertQuery);
$stmt = AE\createTable($conn, $tableName, $columns);
if (!$stmt) {
fatalError("Failed to create table $tableName\n");
}
// The data we test against is in values.php
for ($v = 0; $v < sizeof($values); ++$v) {
// Each value must be inserted twice because the AE and non-AE column are side by side.
$testValues = array();
for ($i = 0; $i < sizeof($values[$v]); ++$i) {
$testValues[] = $values[$v][$i];
$testValues[] = $values[$v][$i];
}
// Insert the data using sqlsrv_prepare()
$stmt = sqlsrv_prepare($conn, $insertQuery, $testValues);
if ($stmt == false) {
fatalError("sqlsrv_prepare failed\n");
}
if (!sqlsrv_execute($stmt)) {
fatalError("sqlsrv_execute failed\n");
}
sqlsrv_free_stmt($stmt);
// Formulate the matrix of SELECT queries and iterate over each index.
$selectQuery = array();
$selectQueryAE = array();
formulateSelectQuery($tableName, $selectQuery, $selectQueryAE, $dataTypes);
for ($i = 0; $i < sizeof($dataTypes); ++$i) {
for ($j = 0; $j < sizeof($dataTypes); ++$j) {
$stmt = sqlsrv_query($conn, $selectQuery[$i][$j]);
if ($stmt == false) {
$convError = sqlsrv_errors();
checkErrors($convError);
if (AE\isDataEncrypted()) {
$stmtAE = sqlsrv_query($conn, $selectQueryAE[$i][$j]);
$convError = sqlsrv_errors();
// if the non-AE conversion fails, certainly the AE conversion
// should fail but only with error 22018 (i.e. conversion not allowed)
if ($stmtAE != false) {
fatalError("AE conversion should have failed. i=$i, j=$j, v=$v\n\n");
}
if ($convError[0][0] != '22018') {
fatalError("AE conversion failed with unexpected error message. i=$i, j=$j, v=$v\n");
}
}
} else {
if ($conversionMatrix[$i][$j] == 'x') {
fatalError("Conversion succeeded, should have failed. i=$i, j=$j, v=$v\n");
}
if (AE\isDataEncrypted()) {
$stmtAE = sqlsrv_query($conn, $selectQueryAE[$i][$j]);
// Check every combination of statement value and conversion.
// The last else if block covers the case where the select
// query worked and the retrieved values are compared.
if ($stmtAE == false) {
if ($conversionMatrixAE[$i][$j] == 'x') {
$convError = sqlsrv_errors();
if ($convError[0][0] != '22018') {
fatalError("AE conversion failed with unexpected error message. i=$i, j=$j, v=$v\n");
}
} else { // $conversionMatrixAE[$i][$j] == 'y'
fatalError("AE conversion failed, should have succeeded. i=$i, j=$j, v=$v\n");
}
} else { // query succeeded
if ($conversionMatrixAE[$i][$j] == 'x') {
fatalError("AE conversion succeeded, should have failed. i=$i, j=$j, v=$v\n");
} elseif ($conversionMatrixAE[$i][$j] == 'y') {
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
$rowAE = sqlsrv_fetch_array($stmtAE, SQLSRV_FETCH_NUMERIC);
// rtrim strips whitespace from the end of the string, which
// takes care of a bug where some conversions lead to extraneous
// whitespace padding the end of the string
if (is_string($row[0])) {
$row[0] = rtrim($row[0]);
$rowAE[0] = rtrim($rowAE[0]);
}
if ($row[0] != $rowAE[0]) {
echo "Values do not match! i=$i, j=$j, v=$v\n";
print_r($row[0]);
print_r($rowAE[0]);
echo "\n";
}
}
}
}
}
}
}
$deleteQuery = "TRUNCATE TABLE $tableName";
$stmt = sqlsrv_query($conn, $deleteQuery);
if ($stmt == false) {
fatalError("Delete statement failed");
}
echo "Step $v done\n";
}
dropTable($conn, $tableName);
sqlsrv_close($conn);
echo "Test successful\n";
?>
--EXPECT--
Step 0 done
Step 1 done
Step 2 done
Step 3 done
Step 4 done
Step 5 done
Step 6 done
Step 7 done
Step 8 done
Step 9 done
Step 10 done
Step 11 done
Step 12 done
Step 13 done
Step 14 done
Step 15 done
Step 16 done
Step 17 done
Step 18 done
Step 19 done
Step 20 done
Test successful

View file

@ -25,7 +25,7 @@ AE\insertRow($conn, $tbname, array("c1_bigint" => 922337203685479936));
// Call stored procedure with SQLSRV_PARAM_OUT
$outSql = "{CALL $spname (?)}";
$bigintOut = 0;
$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_OUT)));
$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_OUT, null, SQLSRV_SQLTYPE_BIGINT)));
sqlsrv_execute($stmt);
printf("Large bigint output:\n");
var_dump($bigintOut);
@ -33,7 +33,7 @@ printf("\n");
// Call stored procedure with SQLSRV_PARAM_INOUT
$bigintOut = 0;
$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_INOUT)));
$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_INOUT, null, SQLSRV_SQLTYPE_BIGINT)));
sqlsrv_execute($stmt);
printf("Large bigint inout:\n");
var_dump($bigintOut);

View file

@ -106,19 +106,14 @@ function testEncryptedWithODBC($msodbcsqlMaj, $server, $connectionOptions)
$connectionOptions['Driver']=$value;
$connectionOptions['ColumnEncryption']='Enabled';
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
} else {
$expected = "Invalid option ColumnEncryption was passed to sqlsrv_connect.";
}
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
connectVerifyOutput($server, $connectionOptions, $expected);
}
function testWrongODBC($msodbcsqlMaj, $server, $connectionOptions)
{
// TODO: this will change once ODBC 17 is officially released
$value = "ODBC Driver 17 for SQL Server";
$value = "ODBC Driver 11 for SQL Server";
if ($msodbcsqlMaj == 17 || $msodbcsqlMaj < 13) {
$value = "ODBC Driver 13 for SQL Server";
}

View file

@ -0,0 +1,112 @@
--TEST--
GitHub issue #678 - Idle Connection Resiliency doesn't work with Connection Pooling
--DESCRIPTION--
Verifies that the issue has been fixed with ODBC 17.1
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_protocol_not_tcp.inc');
require('skipif_version_less_than_2k14.inc'); ?>
--FILE--
<?php
require_once("MsCommon.inc");
function checkODBCVersion($conn)
{
$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer'];
$vers = explode(".", $msodbcsql_ver);
if ($vers[0] >= 17 && $vers[1] > 0){
return true;
} else {
return false;
}
}
function breakConnection($conn, $conn_break)
{
$stmt1 = sqlsrv_query($conn, "SELECT @@SPID");
if (sqlsrv_fetch($stmt1)) {
$spid=sqlsrv_get_field($stmt1, 0);
}
$stmt2 = sqlsrv_prepare($conn_break, "KILL ".$spid);
sqlsrv_execute($stmt2);
sleep(1);
}
// create a connection for create the table and breaking other connections
$conn_break = sqlsrv_connect($server, array("Database"=>$database, "UID"=>$uid, "PWD"=>$pwd));
if (! checkODBCVersion($conn_break)) {
echo "Done\n";
return;
}
$tableName = "test_connres";
dropTable($conn_break, $tableName);
$sql = "CREATE TABLE $tableName (c1 INT, c2 VARCHAR(40))";
$stmt = sqlsrv_query($conn_break, $sql);
$sql = "INSERT INTO $tableName VALUES (?, ?)";
for ($t = 200; $t < 209; $t++) {
$ts = substr(sha1($t), 0, 5);
$params = array($t, $ts);
$stmt = sqlsrv_prepare($conn_break, $sql, $params);
sqlsrv_execute($stmt);
}
// first connection
$connectionInfo = array("Database"=>$database, "UID"=>$uid, "PWD"=>$pwd,
"ConnectionPooling"=>true, "ConnectRetryCount"=>10,
"ConnectRetryInterval"=>10 );
$conn = sqlsrv_connect($server, $connectionInfo);
breakConnection($conn, $conn_break);
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>"buffered"));
if ($stmt === false) {
echo "Error in connection 1.\n";
print_r(sqlsrv_errors());
} else {
$row_count = sqlsrv_num_rows($stmt);
if ($row_count != 9) {
echo "Unexpected $row_count rows in result set.\n";
}
}
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
// second connection
$conn = sqlsrv_connect($server, $connectionInfo);
breakConnection($conn, $conn_break);
// would connection be able to resume here if connection pooling is enabled?
$stmt2 = sqlsrv_query($conn, $query);
if ($stmt2 === false) {
echo "Error in connection 2.\n";
print_r(sqlsrv_errors());
} else {
$num_fields = sqlsrv_num_fields($stmt2);
if ($num_fields != 2) {
echo "Unexpected $num_fields columns in result set.\n";
}
}
dropTable($conn, $tableName);
echo "Done\n";
sqlsrv_free_stmt($stmt2);
sqlsrv_close($conn);
sqlsrv_close($conn_break);
?>
--EXPECT--
Done

View file

@ -0,0 +1,94 @@
--TEST--
GitHub issue #699 - binding integer as output parameter failed
--DESCRIPTION--
This test uses the sample stored procedure provided by the user, in which an
error situation caused the binding to fail with an irrelevant error message about UTF-8 translation. This test proves that this issue has been fixed.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
$connectionOptions = array("CharacterSet"=> "UTF-8", "ConnectionPooling"=>1);
$conn = connect($connectionOptions);
$tableName1 = "table_issue699_1";
$tableName2 = "table_issue699_2";
$procName = "proc_issue699";
dropTable($conn, $tableName1);
dropTable($conn, $tableName2);
dropProc($conn, $procName);
// Create two test tables without encryption
$sql = "CREATE TABLE $tableName1 (correio_electronico NVARCHAR(50), nome NVARCHAR(50), telefones NVARCHAR(15), id_entidade INT)";
$stmt = sqlsrv_query($conn, $sql);
if (!$stmt) {
fatalError("Failed to create table $tableName1\n");
}
$sql = "CREATE TABLE $tableName2 (estado TINYINT NOT NULL DEFAULT 0)";
$stmt = sqlsrv_query($conn, $sql);
if (!$stmt) {
fatalError("Failed to create table $tableName2\n");
}
// Create the stored procedure
$sql = "CREATE PROCEDURE $procName @outparam INT OUTPUT AS
BEGIN
SET @outparam = 100;
INSERT INTO $tableName1 (correio_electronico, nome, telefones, id_entidade)
SELECT 'membros@membros.pt', 'Teste', 'xxx', 1
FROM $tableName2 CC
WHERE CC.estado = 100
BEGIN TRY
SET @outparam = 123
END TRY
BEGIN CATCH
END CATCH
END";
$stmt = sqlsrv_query($conn, $sql);
if (!$stmt) {
fatalError("Error in creating the stored procedure $procName\n");
}
$set_no_count = "";
if (strtoupper(substr(PHP_OS, 0, 3)) === 'LIN') {
// This test, when running outside of Windows, requires unixODBC 2.3.4
// or above (see the list of bug fixes in www.unixodbc.org)
// Add this workaround for Linux platforms
$set_no_count = "SET NOCOUNT ON; ";
}
$sql_callSP = $set_no_count . "{call $procName(?)}";
// Initialize the output parameter to any number
$outParam = 1;
$params = array(array(&$outParam, SQLSRV_PARAM_OUT));
$stmt = sqlsrv_query($conn, $sql_callSP, $params);
if (!$stmt) {
fatalError("Error in calling $procName\n");
}
while ($res = sqlsrv_next_result($stmt));
if ($outParam != 123) {
echo "The output param value $outParam is unexpected!\n";
}
dropTable($conn, $tableName1);
dropTable($conn, $tableName2);
dropProc($conn, $procName);
// Free handles
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
echo "Done\n";
?>
--EXPECT--
Done

File diff suppressed because one or more lines are too long