New upstream version 5.10.1

This commit is contained in:
David Prévot 2023-12-06 14:41:07 +01:00
parent 16459fab38
commit 9583ca32e0
977 changed files with 121 additions and 115368 deletions

View file

@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
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 (skipping questions might delay our responses):
**PHP version**
**PHP SQLSRV or PDO_SQLSRV version**
**Microsoft ODBC Driver version**
**SQL Server version**
**Client operating system**
**Table schema**
**Problem description**
**Expected behavior and actual behavior**
**Repro code or steps to reproduce**

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is.
### Describe the solution you'd like
A clear and concise description of what you want to happen.
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Add any other context about the feature request here.

View file

View file

@ -1,51 +0,0 @@
sudo: required
os: linux
dist: bionic
group: edge
services:
- docker
env:
global:
- REPORT_EXIT_STATUS=1
- ACCEPT_EULA=Y
- PHPSQLDIR=/REPO/msphpsql-dev
- TEST_PHP_SQL_SERVER=sql
- SQLSRV_DBNAME=msphpsql_sqlsrv
- PDOSQLSRV_DBNAME=msphpsql_pdosqlsrv
- TEST_PHP_SQL_UID=sa
- TEST_PHP_SQL_PWD=Password123
before_install:
- docker pull mcr.microsoft.com/mssql/server:2017-latest
install:
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2017-latest
- docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql .
before_script:
- sleep 30
script:
- travis_retry docker run -e TRAVIS_JOB_ID -t -d -w $PHPSQLDIR --name=client --link $TEST_PHP_SQL_SERVER msphpsql-dev
- docker ps -a
- docker logs client
- travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $SQLSRV_DBNAME
- travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $PDOSQLSRV_DBNAME
- travis_retry docker exec client php ./source/pdo_sqlsrv/run-tests.php ./test/functional/pdo_sqlsrv/*.phpt
- travis_retry docker exec client php ./source/sqlsrv/run-tests.php ./test/functional/sqlsrv/*.phpt
- docker exec client bash -c 'for f in ./test/functional/sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true'
- docker exec client bash -c 'for f in ./test/functional/sqlsrv/*.out; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true'
- docker exec client bash -c 'for f in ./test/functional/pdo_sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true'
- docker exec client bash -c 'for f in ./test/functional/pdo_sqlsrv/*.out; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true'
- docker exec client python ./test/functional/setup/cleanup_dbs.py -dbname $SQLSRV_DBNAME
- docker exec client python ./test/functional/setup/cleanup_dbs.py -dbname $PDOSQLSRV_DBNAME
- docker stop client
- docker ps -a
notifications:
email: false

View file

@ -1,1208 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## 5.10.1 - 2022-05-12
Updated PECL release packages. Here is the list of updates:
### Added
- Pull request [#1382](https://github.com/microsoft/msphpsql/pull/1382) - Support for ActiveDirectoryIntegrated authentication
### Fixed
- Pull request [#1374](https://github.com/microsoft/msphpsql/pull/1374) - Fixed ActiveDirectoryMsi Authentication behavior when specified UID by laclefyoshi
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.10.0 - 2022-01-31
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.1
- Support for macOS Monterey, Debian 11, Ubuntu 21.04 and 21.10, Alpine 3.13, 3.14 and 3.15
- Support for Apple M1 ARM64 hardware -- requires [MS ODBC Driver 17.8+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15)
- Adjusted connection keyword and value validation for more flexibility
- Feature Request [#795](https://github.com/microsoft/msphpsql/issues/795) - adding support for [Table-valued parameters](https://github.com/Microsoft/msphpsql/wiki/Features#tvp)
- Feature Request [#1320](https://github.com/microsoft/msphpsql/issues/1320) - allow PDO::ATTR_EMULATE_PREPARES to be set at the connection level
### Removed
- Support for PHP 7.3
- Support for Ubuntu 16.04, Alpine 3.11 and Alpine 3.12
### Fixed
- Issue [#1244](https://github.com/microsoft/msphpsql/issues/1244) - use lower case for object names for PDO::lastInsertId() - pull request [#1245](https://github.com/microsoft/msphpsql/pull/1245) by morozov
- Pull request [#1251](https://github.com/microsoft/msphpsql/pull/1251) - simplified implementations of last insert id and quote
- Issue [#1258](https://github.com/microsoft/msphpsql/issues/1258) - updated pdo_sqlsrv_get_driver_methods as per documentation - pull request [#1259](https://github.com/microsoft/msphpsql/pull/1259)
- Pull request [#1260](https://github.com/microsoft/msphpsql/pull/1260) - cleaned up redundant code
- Issue [#1261](https://github.com/microsoft/msphpsql/issues/1261) - simplified get_field_as_string and made it more robust - pull request [#1265](https://github.com/microsoft/msphpsql/pull/1265)
- Pull request [#1262](https://github.com/microsoft/msphpsql/pull/1262) - simplified parse_param_array in sqlsrv
- Pull request [#1267](https://github.com/microsoft/msphpsql/pull/1267) - replaced the obsolete MACRO ZVAL_NEW_ARR with array_init
- Pull request [#1275](https://github.com/microsoft/msphpsql/pull/1275) - fixed warning compiling core_stmt.cpp by mlocati
- Pull request [#1288](https://github.com/microsoft/msphpsql/pull/1288) - applied mask to pdo quote for binary inputs
- Pull request [#1290](https://github.com/microsoft/msphpsql/pull/1290) - updated list of supported processor architecture
- Issue [#1307](https://github.com/microsoft/msphpsql/issues/1307) - added TVP support to non-procedure statements
- Issue [#1310](https://github.com/microsoft/msphpsql/issues/1310) - adjusted sql_data_type and column size for NULL parameters - pull request [#1311](https://github.com/microsoft/msphpsql/pull/1311) by gjcarrette
- Pull request [#1326](https://github.com/microsoft/msphpsql/pull/1326) - php drivers simply pass Azure AD Authentication to ODBC driver, which will verify the settings
- Issue [#1329](https://github.com/microsoft/msphpsql/issues/1329) - reset sql type and column size for input params
- Issue [#1331](https://github.com/microsoft/msphpsql/issues/1331) - restore PDO::ATTR_ERRMODE if calling PDO::lastInsertId() call fails - pull request [#1330](https://github.com/microsoft/msphpsql/pull/1330) by mpyw and pull request [#1332](https://github.com/microsoft/msphpsql/pull/1332)
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.10.0-beta2 - 2021-12-02
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.1
- Support for Ubuntu 21.10
- Feature Request [#1320](https://github.com/microsoft/msphpsql/issues/1320) - allow PDO::ATTR_EMULATE_PREPARES to be set at the connection level
### Fixed
- Issue [#1307](https://github.com/microsoft/msphpsql/issues/1307) - added TVP support to non-procedure statements
- Issue [#1310](https://github.com/microsoft/msphpsql/issues/1310) - adjusted sql_data_type and column size for NULL parameters - pull request [#1311](https://github.com/microsoft/msphpsql/pull/1311) by gjcarrette
- Pull request [#1326](https://github.com/microsoft/msphpsql/pull/1326) - php drivers simply pass Azure AD Authentication to ODBC driver, which will verify the settings
- Issue [#1329](https://github.com/microsoft/msphpsql/issues/1329) - reset sql type and column size for input params
- Issue [#1331](https://github.com/microsoft/msphpsql/issues/1331) - restore PDO::ATTR_ERRMODE if calling PDO::lastInsertId() call fails - pull request [#1330](https://github.com/microsoft/msphpsql/pull/1330) by mpyw and pull request [#1332](https://github.com/microsoft/msphpsql/pull/1332)
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.10.0-beta1 - 2021-09-08
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.1 RC 1
- Support for Ubuntu 21.04 and Alpine 3.13
- Support for Apple M1 ARM64 hardware (requires [MS ODBC Driver 17.8+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15)
- Feature Request [#795](https://github.com/microsoft/msphpsql/issues/795) - adding support for [Table-valued parameters](https://github.com/Microsoft/msphpsql/wiki/Features#tvp)
### Removed
- Support for Ubuntu 16.04
### Fixed
- Issue [#1244](https://github.com/microsoft/msphpsql/issues/1244) - use lower case for object names for PDO::lastInsertId() - pull request [#1245](https://github.com/microsoft/msphpsql/pull/1245) by morozov
- Pull request [#1251](https://github.com/microsoft/msphpsql/pull/1251) - simplified implementations of last insert id and quote
- Issue [#1258](https://github.com/microsoft/msphpsql/issues/1258) - updated pdo_sqlsrv_get_driver_methods as per documentation - pull request [#1259](https://github.com/microsoft/msphpsql/pull/1259)
- Pull request [#1260](https://github.com/microsoft/msphpsql/pull/1260) - cleaned up redundant code
- Issue [#1261](https://github.com/microsoft/msphpsql/issues/1261) - simplified get_field_as_string and made it more robust - pull request [#1265](https://github.com/microsoft/msphpsql/pull/1265)
- Pull request [#1262](https://github.com/microsoft/msphpsql/pull/1262) - simplified parse_param_array in sqlsrv
- Pull request [#1267](https://github.com/microsoft/msphpsql/pull/1267) - replaced the obsolete MACRO ZVAL_NEW_ARR with array_init
- Pull request [#1275](https://github.com/microsoft/msphpsql/pull/1275) - fixed warning compiling core_stmt.cpp by mlocati
- Pull request [#1288](https://github.com/microsoft/msphpsql/pull/1288) - applied mask to pdo quote for binary inputs
- Pull request [#1290](https://github.com/microsoft/msphpsql/pull/1290) - updated list of supported processor architecture
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.9.0 - 2021-01-29
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.0
- Support for macOS Big Sur, Ubuntu 20.04, Ubuntu 20.10 and Alpine 3.12
- Support for GB18030 locale [#1115](
https://github.com/microsoft/msphpsql/pull/1115)
- Feature Request [#924](https://github.com/microsoft/msphpsql/issues/924) - extended PDO errorinfo to include [additional odbc messages if available](https://docs.microsoft.com/sql/connect/php/pdo-errorinfo) - pull request [#1133](
https://github.com/microsoft/msphpsql/pull/1133)
- [Data Classification with rank info](https://github.com/microsoft/msphpsql/wiki/Features#dataClass), which requires [MS ODBC Driver 17.4.2+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server) and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) or an Azure SQL instance that supports it
- [Azure Active Directory Service Principal authentication support](https://docs.microsoft.com/sql/connect/php/azure-active-directory), which requires [MS ODBC Driver 17.7+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server)
### Removed
- Dropped support for PHP 7.2
- Dropped support for macOS High Sierra, Ubuntu 19.10 and Debian 8
### Fixed
- Pull Request [#1127](https://github.com/microsoft/msphpsql/pull/1127) - removed TSRMLS macros in preparation for PHP 8 by remicollet
- Pull Request [#1136](https://github.com/microsoft/msphpsql/pull/1136) - improved performance when handling decimal numbers as inputs or outputs and removed unncessary conversions for numeric values
- Pull Request [#1143](https://github.com/microsoft/msphpsql/pull/1143) - if an exception occurs when executing a query, it will not change the output parameters
- Pull Request [#1144](https://github.com/microsoft/msphpsql/pull/1144) - use the correct C types when binding output parameters with integer values
- Pull Request [#1146](https://github.com/microsoft/msphpsql/pull/1146) - improved performance when fetching numbers using client buffers
- Pull Request [#1165](https://github.com/microsoft/msphpsql/pull/1165) - set query timeout without using LOCK TIMEOUT, which saves an extra trip to the server
- Issue [#1170](https://github.com/microsoft/msphpsql/issues/1170) - when fetching large data types such as ntext, check more than only the display size - pull request [#1172](https://github.com/microsoft/msphpsql/pull/1172)
- Pull Request [#1205](https://github.com/microsoft/msphpsql/pull/1205) - minimized compilation warnings on Linux and macOS
- Pull Request [#1209](https://github.com/microsoft/msphpsql/pull/1209) - fixed a bug when fetching varbinary max fields as char or wide chars
- Issue [#1210](https://github.com/microsoft/msphpsql/issues/1210) - switched from preview to beta terminology to enable Pickle support
- Issue [#1213](https://github.com/microsoft/msphpsql/issues/1213) - the MACOSX_DEPLOYMENT_TARGET in config files caused linker errors in macOS Big Sur - Pull Request [#1215](https://github.com/microsoft/msphpsql/pull/1215)
- Pull Request [#1226](https://github.com/microsoft/msphpsql/pull/1226) - replaced the problematic strlen function
- Pull Request [#1227](https://github.com/microsoft/msphpsql/pull/1227) - addressed static code analyis issues
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.9.0-beta2 - 2020-12-02
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.0
### Removed
- Dropped support for PHP 7.2
### Fixed
- Pull Request [#1205](https://github.com/microsoft/msphpsql/pull/1205) - minimized compilation warnings on Linux and macOS
- Pull Request [#1209](https://github.com/microsoft/msphpsql/pull/1209) - fixed a bug in fetching varbinary max fields as char or wide chars
- Issue [#1210](https://github.com/microsoft/msphpsql/issues/1210) - switched from preview to beta terminology to enable Pickle support
- Issue [#1213](https://github.com/microsoft/msphpsql/issues/1213) - the MACOSX_DEPLOYMENT_TARGET in config files caused linker errors in macOS Big Sur - Pull Request [#1215](https://github.com/microsoft/msphpsql/pull/1215)
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This preview release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.9.0-preview1 - 2020-10-02
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.0 RC 1
- Support for Ubuntu 20.04 and Alpine 3.12
- Support for GB18030 locale [#1115](
https://github.com/microsoft/msphpsql/pull/1115)
- Feature Request [#924](https://github.com/microsoft/msphpsql/issues/924) - extended PDO errorinfo to include [additional odbc messages if available](https://github.com/microsoft/msphpsql/wiki/Features#pdoErrorInfo) - pull request [#1133](
https://github.com/microsoft/msphpsql/pull/1133)
- [Data Classification with rank info](https://github.com/microsoft/msphpsql/wiki/Features#dataClass), which requires [MS ODBC Driver 17.4.2+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) or an Azure SQL instance that supports it
### Removed
- Dropped support for Ubuntu 19.10 and Debian 8.
### Fixed
- Pull Request [#1127](https://github.com/microsoft/msphpsql/pull/1127) - removal of TSRMLS macros in preparation for PHP 8 by remicollet
- Pull Request [#1136](https://github.com/microsoft/msphpsql/pull/1136) - improved performance when handling decimal numbers as inputs or outputs and removed unncessary conversions for numeric values
- Pull Request [#1143](https://github.com/microsoft/msphpsql/pull/1143) - if an exception occurs when executing a query, will not change the output parameters
- Pull Request [#1144](https://github.com/microsoft/msphpsql/pull/1144) - use the correct C types when binding output parameters with integer values
- Pull Request [#1146](https://github.com/microsoft/msphpsql/pull/1146) - improved performance when fetching numbers using client buffers
- Pull Request [#1165](https://github.com/microsoft/msphpsql/pull/1165) - setting query timeout without using LOCK TIMEOUT, which saves an extra trip to the server
- Issue [#1170](https://github.com/microsoft/msphpsql/issues/1170) - when fetching large data types such as ntext will check more than only the display size - pull request [#1172](https://github.com/microsoft/msphpsql/pull/1172)
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This preview release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.8.1 - 2020-04-15
Updated PECL release packages. Here is the list of updates:
### Fixed
- Pull Request [#1094](https://github.com/microsoft/msphpsql/pull/1094) - Fixed default locale issues in Alpine Linux
- Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1095) - Removed unnecessary data structure to support Client-Side Cursors feature in Alpine Linux
- Pull Request [#1107](https://github.com/microsoft/msphpsql/pull/1107) - Fixed logging issues when both drivers are enabled in Alpine Linux
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.8.0 - 2020-01-31
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 7.4
- Support for [Microsoft ODBC Driver 17.5](
https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) on all platforms
- Support for Debian 10 and Red Hat 8 - require MS ODBC Driver 17.4+
- Support for macOS Catalina, Alpine Linux 3.11 (experimental), and Ubuntu 19.10 - require ODBC Driver 17.5+
- Feature Request [#929](https://github.com/microsoft/msphpsql/issues/929) - new [Language option](https://github.com/microsoft/msphpsql/wiki/Features#language) - Pull Request [#930](https://github.com/microsoft/msphpsql/pull/930)
- [Data Classification Sensitivity Metadata Retrieval](https://github.com/microsoft/msphpsql/wiki/Features#data-classification-sensitivity-metadata) - requires ODBC Driver 17.4.2+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019)
- Feature Request [#1018](https://github.com/microsoft/msphpsql/issues/1018) - support for [PHP extended string types](https://github.com/microsoft/msphpsql/wiki/Features#natlTypes) - Pull Request [#1043](https://github.com/microsoft/msphpsql/pull/1043)
- [Always Encrypted with secure enclaves](https://github.com/microsoft/msphpsql/wiki/Features#alwaysencryptedV2) - requires ODBC Driver 17.4+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019)
- Feature Request [#1063](https://github.com/microsoft/msphpsql/issues/1063) - add configurable options for locale settings in Linux and macOS - Pull Request [#1069](https://github.com/microsoft/msphpsql/pull/1069)
### Removed
- Dropped support for [PHP 7.1](https://www.php.net/supported-versions.php)
- Dropped support for SQL Server 2008 R2, macOS Sierra, Ubuntu 18.10 and Ubuntu 19.04.
### Fixed
- Issue [#570](https://github.com/microsoft/msphpsql/issues/570) - Fixed fetching varbinary data using client buffer with sqlsrv
- Pull Request [#972](https://github.com/microsoft/msphpsql/pull/972) - Removed redundant calls to retrieve the number of columns or rows in the current query result set
- Pull Request [#978](https://github.com/microsoft/msphpsql/pull/978) - PDO_SQLSRV implementation of PDO::getColumnMeta now references cached metadata rather than making an ODBC call every time
- Pull Request [#979](https://github.com/microsoft/msphpsql/pull/979) - Added support for Data Classification Sensitivity metadata retrieval
- Pull Request [#985](https://github.com/microsoft/msphpsql/pull/985) - Fixed memory issues with Data Classification data structures
- Issue [#432](https://github.com/microsoft/msphpsql/issues/432) - Having any invalid UTF-8 name in the connection string will no longer invoke misleading error messages
- Issue [#909](https://github.com/microsoft/msphpsql/issues/909) - Fixed potential exception with locale issues in macOS
- Pull Request [#992](https://github.com/microsoft/msphpsql/pull/992) - Produced the correct error when requesting Data Classification metadata with ODBC drivers prior to 17
- Pull Request [#1001](https://github.com/microsoft/msphpsql/pull/1001) - Fixed compilation issue with PHP 7.4 alpha
- Pull Request [#1004](https://github.com/microsoft/msphpsql/pull/1004) - Fixed another compilation issue with PHP 7.4 alpha
- Pull Request [#1008](https://github.com/microsoft/msphpsql/pull/1008) - Improved data caching when fetching datetime objects
- Pull Request [#1011](https://github.com/microsoft/msphpsql/pull/1011) - Fixed a potential buffer overflow when parsing for escaped braces in the connection string
- Pull Request [#1015](https://github.com/microsoft/msphpsql/pull/1015) - Fixed compilation issues and addressed various memory leaks detected by PHP 7.4 beta 1
- Issue [#1027](https://github.com/microsoft/msphpsql/issues/1027) - Fixed how drivers handle query timeout settings
- Pull Request [#1049](https://github.com/microsoft/msphpsql/pull/1049) - Performance improvement for fetching from tables with many columns - cached the derived php types with column metadata to streamline data retrieval
- Pull Request [#1068](https://github.com/microsoft/msphpsql/pull/1068) - Some cosmetic changes to source code as per suggestions from a static analysis tool
- Issue [#1079](https://github.com/microsoft/msphpsql/issues/1079) - Support sql_variant types when using client buffers
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
- Alpine Linux support is currently experimental. More robust support will be added in future releases
### Known Issues
- In Alpine Linux, the Client-Side Cursors feature may cause an access violation if both sqlsrv and pdo_sqlsrv are enabled. Either enable only sqlsrv or pdo_sqlsrv, or build PHP from source by compiling the drivers statically.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.7.1-preview - 2019-12-03
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 7.4
- Support for Red Hat 8 and macOS Catalina (10.15)
- Feature Request [#1018](https://github.com/microsoft/msphpsql/issues/1018) - support for [PHP extended string types](https://github.com/microsoft/msphpsql/wiki/Features#natlTypes) - Pull Request [#1043](https://github.com/microsoft/msphpsql/pull/1043)
- [Always Encrypted with secure enclaves](https://github.com/microsoft/msphpsql/wiki/Features#alwaysencryptedV2), which requires [MS ODBC Driver 17.4+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019)
### Removed
- Dropped support for [PHP 7.1](https://www.php.net/supported-versions.php)
### Fixed
- Issue [#1027](https://github.com/microsoft/msphpsql/issues/1027) - Fixed how drivers handle query timeout settings
- Pull Request [#1049](https://github.com/microsoft/msphpsql/pull/1049) - performance improvement for fetching from tables with many columns - cached the derived php types with column metadata to streamline data retrieval
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Data Classification metadata retrieval requires ODBC Driver 17.4.2.1+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019)
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.7.0-preview - 2019-09-05
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 7.4 RC 1
- Support for Linux Ubuntu 19.04 and Debian 10
- Feature Request [#929](https://github.com/microsoft/msphpsql/issues/929) - new [Language option](https://github.com/microsoft/msphpsql/wiki/Features#language) - Pull Request [#930](https://github.com/microsoft/msphpsql/pull/930)
- [Data Classification Sensitivity Metadata Retrieval](https://github.com/microsoft/msphpsql/wiki/Features#data-classification-sensitivity-metadata), which requires [MS ODBC Driver 17.2+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [SQL Server 2019 release candidate](https://docs.microsoft.com/sql/sql-server/sql-server-ver15-release-notes?view=sqlallproducts-allversions#-release-candidate-rc)
### Removed
- Dropped support for Ubuntu 18.10
### Fixed
- Issue [#570](https://github.com/microsoft/msphpsql/issues/570) - Fixed fetching varbinary data using client buffer with sqlsrv
- Pull Request [#972](https://github.com/microsoft/msphpsql/pull/972) - Removed redundant calls to retrieve the number of columns or rows in the current query result set
- Pull Request [#978](https://github.com/microsoft/msphpsql/pull/978) - PDO_SQLSRV implementation of PDO::getColumnMeta now references cached metadata rather than making an ODBC call every time
- Pull Request [#979](https://github.com/microsoft/msphpsql/pull/979) - Added support for data classification Sensitivity metadata retrieval
- Pull Request [#985](https://github.com/microsoft/msphpsql/pull/985) - Fixed memory issues with data classification data structures
- Issue [#432](https://github.com/microsoft/msphpsql/issues/432) - Having any invalid UTF-8 name in the connection string will no longer invoke misleading error messages
- Issue [#909](https://github.com/microsoft/msphpsql/issues/909) - Fixed potential exception with locale issues in macOS
- Pull Request [#992](https://github.com/microsoft/msphpsql/pull/992) - Produced the correct error when requesting Data Classification metadata with ODBC drivers prior to 17
- Pull Request [#1001](https://github.com/microsoft/msphpsql/pull/1001) - Fixed compilation issue with PHP 7.4 alpha
- Pull Request [#1004](https://github.com/microsoft/msphpsql/pull/1004) - Fixed another compilation issue with PHP 7.4 alpha
- Pull Request [#1008](https://github.com/microsoft/msphpsql/pull/1008) - Improved data caching when fetching datetime objects
- Pull Request [#1011](https://github.com/microsoft/msphpsql/pull/1011) - Fixed a potential buffer overflow when parsing for escaped braces in the connection string
- Pull Request [#1015](https://github.com/microsoft/msphpsql/pull/1015) - Fixed compilation issues and addressed various memory leaks detected by PHP 7.4 beta 1
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Data Classification metadata retrieval is not compatible with ODBC Driver 17.4.1
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
## 5.6.1 - 2019-03-19
Updated PECL release packages. Here is the list of updates:
### Fixed
- Issue [#937](https://github.com/Microsoft/msphpsql/issues/937) - fixed assumptions made when calculating field or column metadata which may have resulted in application termination
- Issue [#955](https://github.com/Microsoft/msphpsql/issues/955) - modified sqlsrv config file such that it can be compiled independently of pdo_sqlsrv
- Pull Request [#946](https://github.com/Microsoft/msphpsql/pull/946) - fixed PDOStatement::getColumnMeta() to return false when something goes wrong
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
- In SUSE 15, Azure Active Directory connections may fail if PHP is installed from packages (Issue [#934](https://github.com/Microsoft/msphpsql/issues/934))
## 5.6.0 - 2019-02-15
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for PHP 7.3
- Added support for Linux SUSE 15, Ubuntu 18.10 and mac OS Mojave
- Feature Request [#415](https://github.com/Microsoft/msphpsql/pull/886) - new options at connection and statement levels for both drivers for formatting decimal values in the fetched results
- Added support for Azure AD Access Token (in Linux / macOS this requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [unixODBC](http://www.unixodbc.org/) 2.3.6+)
- Added support for Authentication with Azure Active Directory using Managed Identity for Azure Resources (requires [MS ODBC Driver 17.3+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server))
- Feature Request [#842](https://github.com/Microsoft/msphpsql/pull/842) - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects
- Feature Request [#844](https://github.com/Microsoft/msphpsql/pull/844) - add ReturnDatesAsStrings option to statement level for sqlsrv
### Removed
- Dropped support for Ubuntu 17.10
- Dropped support for PHP 7.0 - [Version 5.3](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver?view=sql-server-2017) is the last to support PHP 7.0.
### Fixed
- Issue [#434](https://github.com/Microsoft/msphpsql/issues/434) - To avoid possible crashes, before freeing stmt in the destructor check if its dbh driver data is NULL
- Pull Request [#833](https://github.com/Microsoft/msphpsql/pull/833) - Streamlined the error handling to remove a potential cause of crash
- Pull Request [#836](https://github.com/Microsoft/msphpsql/pull/836) - Modified the config files to enable Spectre Mitigations (use /Qspectre switch) for PHP 7.2 (see related Request [#878](https://github.com/Microsoft/msphpsql/pull/878))
- Pull Request [#854](https://github.com/Microsoft/msphpsql/pull/854) - Clear Azure Key Vault data after connection attributes are successfully set or when exception is thrown
- Pull Request [#855](https://github.com/Microsoft/msphpsql/pull/855) - Improved performance by saving meta data before fetching and skipping unnecessary conversions for numeric data
- Pull Request [#865](https://github.com/Microsoft/msphpsql/pull/865) - Corrected the way SQLPutData and SQLParamData are used when sending stream data to the server
- Pull Request [#878](https://github.com/Microsoft/msphpsql/pull/878) - Modified the config files to enable Spectre Mitigations for PHP 7.1 (see related Request [#836](https://github.com/Microsoft/msphpsql/pull/836))
- Pull Request [#891](https://github.com/Microsoft/msphpsql/pull/891) - Improved performance of Unicode conversions
- Pull Request [#892](https://github.com/Microsoft/msphpsql/pull/892) - Removed warning messages while compiling extensions
- Pull Request [#904](https://github.com/Microsoft/msphpsql/pull/904) - Enabled compiling extensions statically into PHP
- Pull Request [#907](https://github.com/Microsoft/msphpsql/pull/907) - Initialized output param buffer when allocating extra space
- Pull Request [#919](https://github.com/Microsoft/msphpsql/pull/919) - Initialized a boolean variable before passing it by reference into a function that will modify its value
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
## 5.5.0-preview - 2018-12-07
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for PHP 7.3.0
- Added support for Linux Ubuntu 18.10 and mac OS Mojave
- Feature Request [#415](https://github.com/Microsoft/msphpsql/pull/886) - new options at connection and statement levels for both drivers for formatting decimal values in the fetched results
### Fixed
- Pull Request [#854](https://github.com/Microsoft/msphpsql/pull/854) - Clear Azure Key Vault data after connection attributes are successfully set or when exception is thrown
- Pull Request [#855](https://github.com/Microsoft/msphpsql/pull/855) - Improved performance by saving meta data before fetching and skipping unnecessary conversions for numeric data
- Pull Request [#865](https://github.com/Microsoft/msphpsql/pull/865) - Corrected the way SQLPutData and SQLParamData are used when sending stream data to the server
- Pull Request [#878](https://github.com/Microsoft/msphpsql/pull/878) - Modified the config files to enable Spectre Mitigations for PHP 7.1 (see related Request [#836](https://github.com/Microsoft/msphpsql/pull/836))
- Pull Request [#891](https://github.com/Microsoft/msphpsql/pull/891) - Improved performance of Unicode conversions
- Pull Request [#892](https://github.com/Microsoft/msphpsql/pull/892) - Removed warning messages while compiling extensions
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
- With ColumnEncryption enabled, fetching varbinary(max), varchar(max) or nvarchar(max) may fail with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/)
## 5.4.0-preview - 2018-09-24
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for PHP 7.3.0 RC 1
- Added support for Azure AD Access Token (in Linux / macOS this requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [unixODBC](http://www.unixodbc.org/) 2.3.6+)
- Feature Request [#842](https://github.com/Microsoft/msphpsql/pull/842) - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects
- Feature Request [#844](https://github.com/Microsoft/msphpsql/pull/844) - add ReturnDatesAsStrings option to statement level for sqlsrv
- Compatible with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/)
### Removed
- Dropped support for Ubuntu 17.10
- Dropped support for PHP 7.0 - [Version 5.3](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver?view=sql-server-2017) is the last to support PHP 7.0.
### Fixed
- Issue [#434](https://github.com/Microsoft/msphpsql/issues/434) - To avoid the pitfall that could result in a crash, before freeing stmt in the destructor check if its dbh driver data is NULL
- Pull Request [#836](https://github.com/Microsoft/msphpsql/pull/836) - Modified the config files to enable Spectre Mitigations (use /Qspectre switch) for PHP 7.2
- Pull Request [#833](https://github.com/Microsoft/msphpsql/pull/833) - Streamlined the error handling to remove a potential cause of crash
### 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 connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
- With ColumnEncryption enabled, fetching varbinary(max), varchar(max) or nvarchar(max) may fail with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/)
## 5.3.0 - 2018-07-20
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for Azure Key Vault for Always Encrypted functionality. Always Encrypted functionality is supported on Linux and macOS through Azure Key Vault
- Added support for connection resiliency on Linux and macOS (requires version 17.2 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017))
- Added support for macOS High Sierra (requires version 17 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017))
- Added support for Ubuntu 18.04 (requires version 17.2 or higher of the [ODBC driver](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 connections (fixed in MS ODBC Driver 17.1)
- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling (fixed in MS ODBC Driver 17.1)
- Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameters fails when the query in the stored procedure returns no data. The test case has been added to the test lab.
- Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation
- Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38)
- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - 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 so 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 truncation 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 connecting will not work
- Always Encrypted 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. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- [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 is 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 diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
## 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
- Added support for Always Encrypted with basic CRUD functionalities (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- Support for Windows Certificate Store (use connection keyword ColumnEncryption)
- Support for inserting into and modifying an encrypted column
- Support for fetching from an encrypted column
- Added support for PHP 7.2
- Added support for MS ODBC Driver 17
- Added support for Ubuntu 17 (requires MS ODBC Driver 17)
- Added support for Debian 9 (requires MS ODBC Driver 17)
- Added support for SUSE 12
- Added Driver option to set the MS ODBC driver, Added "Driver" option, valid values are "ODBC Driver 17 for SQL Server", "ODBC Driver 13 for SQL Server", and "ODBC Driver 11 for SQL Server"
- The default driver is ODBC Driver 17 for SQL Server
### Changed
- Implementation of PDO::lastInsertId($name) to return the last inserted sequence number if the sequence name is supplied to the function ([lastInsertId](https://github.com/Microsoft/msphpsql/wiki/Features#lastinsertid))
- Added immediate binding for security, making it necessary to load PDO before PDO_SQLSRV; full install instructions [here](https://github.com/Microsoft/msphpsql/blob/master/Linux-mac-install.md) and [here](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver)
### Fixed
- Issue [#555](https://github.com/Microsoft/msphpsql/issues/555) - Hebrew strings truncation (requires MS ODBC Driver 17)
- Adjusted precisions for numeric/decimal inputs with Always Encrypted
- Support for non-UTF8 locales in Linux and macOS
- Fixed crash caused by executing an invalid query in a transaction (Issue [#434](https://github.com/Microsoft/msphpsql/issues/434))
- Added error handling for using PDO::SQLSRV_ATTR_DIRECT_QUERY or PDO::ATTR_EMULATE_PREPARES in a Column Encryption enabled connection
- Added error handling for binding TEXT, NTEXT or IMAGE as output parameter (Issue [#231](https://github.com/Microsoft/msphpsql/issues/231))
- PDO::quote with string containing ASCII NUL character (Issue [#538]( https://github.com/Microsoft/msphpsql/issues/538))
- Decimal types with no decimals are correctly handled when AE is enabled (PR [#544](https://github.com/Microsoft/msphpsql/pull/544))
- BIGINT as an output param no longer results in value out of range exception when the returned value is larger than a maximum integer ([PR #567](https://github.com/Microsoft/msphpsql/pull/567))
### Removed
- Dropped support for Ubuntu 15
- Supplying tablename into PDO::lastInsertId($name) no longer return the last inserted row ([lastInsertId](https://github.com/Microsoft/msphpsql/wiki/Features#lastinsertid))
### Limitations
- Always Encrypted is not supported in Linux and macOS
- 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 functionalities are only supported using MS ODBC Driver 17
- [Always Encrypted limitations](https://github.com/Microsoft/msphpsql/wiki/Features#aelimitation)
- When using sqlsrv_query with Always Encrypted feature, SQL type has to be specified for each input (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- No support for inout / output params when using sql_variant type
### Known Issues
- Connection pooling on Linux doesn't work properly when using MS ODBC Driver 17
- 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/Connection-Pooling-on-Linux-and-Mac)
- Connection with Connection Resiliency enabled does not resume properly with Connection Pooling (Issue [#678](https://github.com/Microsoft/msphpsql/issues/678))
- With ColumnEncryption enabled, calling stored procedure with XML parameter does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
- Cannot connect with both Connection Resiliency enabled and ColumnEncryption enabled (Issue [#577](https://github.com/Microsoft/msphpsql/issues/577))
- With ColumnEncryption enabled, retrieving a negative decimal value as output parameter causes truncation of the last digit (Issue [#705](https://github.com/Microsoft/msphpsql/issues/705))
- With ColumnEncryption enabled, cannot insert a double into a decimal column with precision and scale of (38, 38) (Issue [#706](https://github.com/Microsoft/msphpsql/issues/706))
- With ColumnEncryption enabled, when fetching decimals as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT, floats are returned, not integers (Issue [#707](https://github.com/Microsoft/msphpsql/issues/707))
## Windows/Linux/macOS 5.2.0-RC - 2017-12-20
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for Ubuntu 17 (requires [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview))
- Added support for Debian 9 (requires [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview))
### Fixed
- Issue [#555](https://github.com/Microsoft/msphpsql/issues/555) - Hebrew strings truncation (requires [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview))
- Issue [#615](https://github.com/Microsoft/msphpsql/issues/615) - Added error handling when fetching varchar(max) as a stream with Always Encrypted
- Adjusted precisions for numeric/decimal inputs with Always Encrypted
- Fixed bugs when binding parameters with Always Encrypted
- Fixed warnings as per Prefast code analysis
### Limitations
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. The subsequent locale setting will not work
- Always Encrypted functionalities are only supported using [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview)
- ODBC binaries for macOS available upon request
- MSODBC 17 preview msodbcsql.msi only works in Windows10
- [Always Encrypted limitations](https://github.com/Microsoft/msphpsql/wiki/Features#aelimitation)
- When using sqlsrv_query with Always Encrypted feature, SQL type has to be specified for each input (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- No support for inout / output params when using sql_variant type
### Known Issues
- Connection pooling on Linux doesn't work properly when using the MSODBC17 preview
- 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/macOS 5.1.2-preview - 2017-11-21
Updated PECL release packages. Here is the list of updates:
### Fixed
- Support for non-UTF8 locales in Linux and macOS
- Fixed crash caused by executing an invalid query in a transaction (Issue [#434](https://github.com/Microsoft/msphpsql/issues/434))
- Fixed regression in sqlsrv_next_result returning a no fields error when the active result set is null (Issue [#581](https://github.com/Microsoft/msphpsql/issues/581))
- Fixed incorrect active result set when sqlsrv_next_result or PDOStatement::nextRowset is called when Column Encryption is enabled (Issue [#574](https://github.com/Microsoft/msphpsql/issues/574))
- Fixed data corruption in fetching from an encrypted max column after calling sqlsrv_next_result or PDOStatemet::nextRowset (Issue [#580](https://github.com/Microsoft/msphpsql/issues/580))
- Added error handling for using PDO::SQLSRV_ATTR_DIRECT_QUERY or PDO::ATTR_EMULATE_PREPARES in a Column Encryption enabled connection
- Added error handling for binding TEXT, NTEXT or IMAGE as output parameter (Issue [#231](https://github.com/Microsoft/msphpsql/issues/231))
### Limitations
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. The subsequent locale setting will not work
- Always Encrypted functionalities are only supported using [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview)
- ODBC binaries for macOS available upon request
- MSODBC 17 preview msodbcsql.msi only works in Windows10
- [Always Encrypted limitations](https://github.com/Microsoft/msphpsql/wiki/Features#aelimitation)
- When using sqlsrv_query with Always Encrypted feature, SQL type has to be specified for each input (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- No support for inout / output params when using sql_variant type
### Known Issues
- Binding decimal input as a string when Column Encryption is enabled may change the precision of the input
- Connection pooling on Linux doesn't work properly when using the MSODBC17 preview
- 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux 5.1.1-preview - 2017-10-20
Updated PECL release packages. Here is the list of updates:
### Fixed
- PDO::quote with string containing ASCII NUL character (Issue [#538]( https://github.com/Microsoft/msphpsql/issues/538))
- Appropriate error message is returned when calling nextRowset() or sqlsrv_next_result() on an empty result set (issue [#507 ](https://github.com/Microsoft/msphpsql/issues/507))
- Decimal types with no decimals are correctly handled when AE is enabled (PR [#544](https://github.com/Microsoft/msphpsql/pull/544))
- Search for installed ODBC drivers in Linux/macOS first before attempting to connect using the default ODBC driver
- BIGINT as an output param no longer results in value out of range exception when the returned value is larger than a maximum integer ([PR #567](https://github.com/Microsoft/msphpsql/pull/567))
### Limitations
- Always Encrypted functionalities are only supported using [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview)
- ODBC binaries for macOS available upon request
- MSODBC 17 preview msodbcsql.msi only works for Windows10
- [Always Encrypted limitations](https://github.com/Microsoft/msphpsql/wiki/Features#aelimitation)
- When using sqlsrv_query with Always Encrypted feature, SQL type has to be specified for each input (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- No support for inout / output params when using sql_variant type
### Known Issues
- Connection pooling on Linux doesn't work properly when using the MSODBC17 preview
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux 5.1.0-preview - 2017-09-15
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for SUSE 12
- Added support for Always Encrypted with basic CRUD functionalities (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- Support for Windows Certificate Store (use connection keyword ColumnEncryption)
- Support for custom key store provider (use connection keywords ColumnEncryption, CEKeystoreProvider, CEKeystoreName, CEKeystoreEncryptKey)
- Support for inserting into an encrypted column
- Support for fetching from an encrypted column
- Added support for MSODBC 17 preview
- Added Driver option to set the ODBC driver, Added"Driver" option, valid values are ODBC Driver 13 for SQL Server,ODBC Driver 11 for SQL Server, and ODBC Driver 17 for SQL Server
- If the user intends to use the new Always Encrypted features, we recommend you to specify explicitly the 'Driver' option to 'ODBC Driver 17 for SQL Server' in the connection string
### Limitations
- Always Encrypted functionalities are only supported using [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview)
- ODBC binaries for macOS available upon request
- MSODBC 17 preview msodbcsql.msi only works for Windows10
- [Always Encrypted limitations](https://github.com/Microsoft/msphpsql/wiki/Features#aelimitation)
- when using sqlsrv_query with Always Encrypted feature, SQL type has to be specified for each input (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- No support for inout / output params when using sql_variant type
### Known Issues
- Connection pooling on Linux doesn't work properly if the user uses the MSODBC17 preview
- PDO::quote returns truncated string with garbage characters appended if the string contains a ASCII NUL ('/0') character
- Binding decimal type when using Always Encrypted in the SQLSRV x64 driver returns an error during insertion when the input does not have any decimal places
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 5.0.0-preview - 2017-07-31
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for PHP 7.2 Beta 1
### Changed
- Implementation of PDO::lastInsertId($name) to return the last inserted sequence number if the sequence name is supplied to the function ([lastInsertId](https://github.com/Microsoft/msphpsql/wiki/Features#lastinsertid))
### Removed
- No longer support Ubuntu 15
- Supplying tablename into PDO::lastInsertId($name) no longer return the last inserted row ([lastInsertId](https://github.com/Microsoft/msphpsql/wiki/Features#lastinsertid))
### Limitation
- No support for inout / output params when using sql_variant type
### Known Issues
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 4.3.0 - 2017-07-06
Production Ready releasefor SQLSRV and PDO_SQLSRV drivers on Sierra, El Capitan, Debian 8, Ubuntu 15, Ubuntu 16, CentOS 7, and Windows. Here is the changlog since the last Production Ready release.
### Added
- Added Unicode Column name support ([issue #138](https://github.com/Microsoft/msphpsql/issues/138)).
- Support for Always On Availability groups via Transparent Network IP Resolution ([TNIR](https://github.com/Microsoft/msphpsql/wiki/Features#TNIR))
- Added support for sql_variant data type with limitation ([issue #51](https://github.com/Microsoft/msphpsql/issues/51)and[issue #127](https://github.com/Microsoft/msphpsql/issues/127))
- Support drivers on Debian Jessie (tested on Debian 8.7)
- Connection Resiliencysupport in Windows
- Connection poolingsupport for Linux and macOS
- Support forMac(El Capitan and above)
- Azure Active Directory Authentication with ActiveDirectoryPassword and SqlPassword
### Fixed
- Fixed PECL installation errors when PHP was installed from source ([issue #213](https://github.com/Microsoft/msphpsql/issues/213)).
- Fixed the assertion error (Linux) when fetching data from a binary column using the binary encoding ([issue #226](https://github.com/Microsoft/msphpsql/issues/226)).
- Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)).
- Fixed a memory leak in closing connection resources.
- Fixed load ordering issue in MacOS ([issue #417](https://github.com/Microsoft/msphpsql/issues/417))
- Added a workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled.
- Fixed the issue with driver loading order in macOS
- Fixed null returned when an empty string is set to an output parameter ([issue #308](https://github.com/Microsoft/msphpsql/issues/308)).
- #### Fixed in SQLSRV
- Fixed sqlsrv client buffer size to only allow positive integers ([issue #228](https://github.com/Microsoft/msphpsql/issues/228)).
- Fixed sqlsrv_num_rows() when the client buffered result is null ([issue #330](https://github.com/Microsoft/msphpsql/issues/330)).
- Fixed issues with sqlsrv_has_rows() to prevent it from moving statement cursor ([issue #37](https://github.com/Microsoft/msphpsql/issues/37)).
- Fixed conversion warnings because of some const chars ([issue #332](https://github.com/Microsoft/msphpsql/issues/332)).
- Fixed debug abort error when building the driver in debug mode with PHP 7.1.
- Fixed string truncation when binding varchar(max), nvarchar(max), varbinary(max), and xml types ([issue #231](https://github.com/Microsoft/msphpsql/issues/231)).
- Fixed fatal error when fetching empty nvarchar ([issue #69](https://github.com/Microsoft/msphpsql/issues/69)).
- Fixed fatal error when calling sqlsrv_fetch() with an out of bound offset for SQLSRV_SCROLL_ABSOLUTE ([issue #223](https://github.com/Microsoft/msphpsql/issues/223)).
- #### Fixed in PDO_SQLSRV
- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)).
- Improved performance by implementing a cache to store column SQL types and display sizes ([issue #189](https://github.com/Microsoft/msphpsql/issues/189)).
- Fixed segmentation fault with PDOStatement::getColumnMeta() when the supplied column index is out of range ([issue #224](https://github.com/Microsoft/msphpsql/issues/224)).
- Fixed issue with the unsupported attribute PDO::ATTR_PERSISTENT in connection ([issue #65](https://github.com/Microsoft/msphpsql/issues/65)).
- Fixed the issue with executing DELETE operation on a non-existent value ([issue #336](https://github.com/Microsoft/msphpsql/issues/336)).
- Fixed incorrectly binding of unicode parameter when emulate prepare is on and the encoding is set at the statement level ([issue #92](https://github.com/Microsoft/msphpsql/issues/92)).
- Fixed binary column binding when emulate prepare is on ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)).
- Fixed wrong value returned when fetching varbinary value on Linux ([issue #270](https://github.com/Microsoft/msphpsql/issues/270)).
- Fixed binary data not returned when the column is bound by name ([issue #35](https://github.com/Microsoft/msphpsql/issues/35)).
- Fixed exception thrown on closeCursor() when the statement has not been executed ([issue #267](https://github.com/Microsoft/msphpsql/issues/267)).
### Limitation
- No support for inout / output params when using sql_variant type
### Known Issues
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 4.3.0-RC1 - 2017-06-21
Here is the list of updates:
### Added
- Transparent Network IP Resolution ([TNIR](https://github.com/Microsoft/msphpsql/wiki/Features#TNIR)) feature.
### Fixed
- Fixed a memory leak in closing connection resources.
- Fixed load ordering issue in MacOS ([issue #417](https://github.com/Microsoft/msphpsql/issues/417))
### Limitation
- No support for inout / output params when using sql_variant type
### Known Issues
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 4.2.0-preview - 2017-05-19
Here is the list of updates:
### Added
- Added support for sql_variant data type with limitation ([issue #51](https://github.com/Microsoft/msphpsql/issues/51) and [issue #127](https://github.com/Microsoft/msphpsql/issues/127))
- Support drivers on Debian Jessie (tested on Debian 8.7)
### Fixed
- Increased Test Coverage to 75%
- Bug fixes after running static analysis
### Limitation
- No support for inout / output params when using sql_variant type
### Known Issues
- User defined data types
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 4.1.9-preview - 2017-05-08
- Updated documentation for Readme regarding instructions for Linux and MAC
- Updated PECL release packages. Here is the list of updates:
### Added
- Azure Active Directory Authentication with ActiveDirectoryPassword and SqlPassword
### Fixed
- Fixed output parameter returning garbage when the parameter is initialized to a type that is different from the output type ([issue #378](https://github.com/Microsoft/msphpsql/issues/378)).
#### PDO_SQLSRV only
- Fixed incorrectly binding of unicode parameter when emulate prepare is on and the encoding is set at the statement level ([issue #92](https://github.com/Microsoft/msphpsql/issues/92)).
- Fixed binary column binding when emulate prepare is on ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)).
### Known Issues
- User defined data types and SQL_VARIANT ([issue #127](https://github.com/Microsoft/msphpsql/issues/127)).
- When pooling is enabled in Linux or MAC
- unixODBC 2.3.1 (Linux) and unixODBC 2.3.4 (MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 4.1.8-preview - 2017-04-10
Updated documentation for Readme regarding instructions for Linux and MAC
Updated PECL release packages. Here is the list of updates:
### Added
- [Connection Resiliency](https://github.com/Microsoft/msphpsql/wiki/Connection-Resiliency) now supported in Windows
- [Connection pooling](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) now works in MAC
### Fixed
#### SQLSRV and PDO_SQLSRV
- Added a workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled.
- Fixed the issue in MAC such that which driver is loaded first no longer matters.
#### SQLSRV only
- Fixed sqlsrv_num_rows() when the client buffered result is null ([issue #330](https://github.com/Microsoft/msphpsql/issues/330)).
- Fixed conversion warnings because of some const chars ([issue #332](https://github.com/Microsoft/msphpsql/issues/332)).
#### PDO_SQLSRV only
- Improved performance by implementing a cache to store column SQL types and display sizes ([issue #189](https://github.com/Microsoft/msphpsql/issues/189)).
- Fixed issue with the unsupported attribute PDO::ATTR_PERSISTENT in connection ([issue #65](https://github.com/Microsoft/msphpsql/issues/65)).
- Fixed the issue when deleting something that doesn't exist ([issue #336](https://github.com/Microsoft/msphpsql/issues/336)).
### Known Issues
- User defined data types and SQL_VARIANT ([issue #127](https://github.com/Microsoft/msphpsql/issues/127)).
- Binary column binding with emulate prepare ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)).
- When pooling is enabled in Linux or MAC
- unixODBC 2.3.1 (Linux) and unixODBC 2.3.4 (MAC) 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 4.1.7-preview - 2017-03-07
Updated PECL release packages. Here is the list of updates:
### Added
- The early technical preview (ETP) for SQLSRV and PDO_SQLSRV drivers for MAC with basic functionalities is now available. Both drivers has been built and tested on MAC OS version El Capitan (OS X 10.11).
### Fixed
#### SQLSRV and PDO_SQLSRV
- Fixed null returned when an empty string is set to an output parameter ([issue #308](https://github.com/Microsoft/msphpsql/issues/308)).
- Fixed memory leaks in buffered result sets.
- Fixed clang compile errors.
#### SQLSRV only
- Fixed debug abort error when building the driver in debug mode with PHP 7.1.
- Fixed string truncation when binding varchar(max), nvarchar(max), varbinary(max), and xml types ([issue #231](https://github.com/Microsoft/msphpsql/issues/231)).
- Fixed fatal error when fetching empty nvarchar ([issue #69](https://github.com/Microsoft/msphpsql/issues/69)).
- Fixed fatal error when calling sqlsrv_fetch() with an out of bound offset for SQLSRV_SCROLL_ABSOLUTE ([issue #223](https://github.com/Microsoft/msphpsql/issues/223)).
#### PDO_SQLSRV only
- Fixed wrong value returned when fetching varbinary value on Linux ([issue #270](https://github.com/Microsoft/msphpsql/issues/270)).
- Fixed binary data not returned when the column is bound by name ([issue #35](https://github.com/Microsoft/msphpsql/issues/35)).
- Fixed exception thrown on closeCursor() when the statement has not been executed ([issue #267](https://github.com/Microsoft/msphpsql/issues/267)).
### Known Issues
- User defined data types and SQL_VARIANT ([issue #127](https://github.com/Microsoft/msphpsql/issues/127)).
- Binary column binding with emulate prepare ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)).
- Segmentation fault may result when an unsupported attribute is used for connection.
#### MAC only
- If loading both sqlsrv and pdo_sqlsrv, the order matters (even when dynamically). For PDO_SQLSRV scripts, load pdo_sqlsrv.so first. For SQLSRV scripts, load sqlsrv.so first.
- Connection pooling not working.
## Windows/Linux 4.1.6 - 2017-02-03
Updated PECL release packages. Here is the list of updates:
### Added
- Merged Linux and Windows code.
- Enabled connection pooling with unixODBC. To enable pooling:
- in odbcinst.ini, add `Pooling=Yes` to the `[ODBC]` section and a positive `CPTimeout` value to `[ODBC Driver 13 for SQL Server]` section. See http://www.unixodbc.org/doc/conn_pool.html for detailed instructions.
###Fixed
- Fixed issues with sqlsrv_has_rows() to prevent it from moving statement cursor ([issue #37](https://github.com/Microsoft/msphpsql/issues/37)).
- Fixed sqlsrv client buffer size to only allow positive integers ([issue #228](https://github.com/Microsoft/msphpsql/issues/228)).
- Fixed PECL installation errors when PHP was installed from source ([issue #213](https://github.com/Microsoft/msphpsql/issues/213)).
- Fixed segmentation fault with PDOStatement::getColumnMeta() when the supplied column index is out of range ([issue #224](https://github.com/Microsoft/msphpsql/issues/224)).
- Fixed the assertion error (Linux) when fetching data from a binary column using the binary encoding ([issue #226](https://github.com/Microsoft/msphpsql/issues/226)).
## Windows 4.1.5 - 2017-01-19
Updated Windows drivers (4.1.5) compiled with PHP 7.0.14 and 7.1 are available. Here is the list of updates:
### Added
- Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)).
###Fixed
- Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)).
- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)).
### Changed
- Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source:
- if you are building the driver from source using PHP source, copy the "shared" folder as a subfolder to both the sqlsrv and pdo_sqlsrv folders.
## Linux 4.0.8 - 2016-12-19
Production release of Linux drivers is available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. Here is the list of updates:
### Added
- Added `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` attribute support in PDO_SQLSRV driver.`SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag handles numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example,
`$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);`
If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`.
Note for exceptions:
- When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string.
- When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off.
- Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)).
###Fixed
- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)).
- Fixed precision issues when double data type returned as strings using buffered queries in PDO_SQLSRV driver.
- Fixed issue with buffered cursor in PDO_SQLSRV driver when CharacterSet is UTF-8 ([issue #192](https://github.com/Microsoft/msphpsql/issues/192)).
- Fixed segmentation fault in error cases when error message is returned with emulate prepare attribute is set to true in PDO_SQLSRV driver.
- Fixed issue with empty output parameters on stored procedure([issue #182](https://github.com/Microsoft/msphpsql/issues/182)).
- Fixed memory leaks in buffered queries.
## Linux 4.0.7 - 2016-11-23
Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2.
### Added
- Ported buffered cursor to Linux.
### Changed
- Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source, use "packagize" script as follows:
- if you are using the phpize, clone or download the “source”, run the script within the “source” directory and then run phpize.
- if you are building the driver from source using PHP source, give the path to the PHP source to the script.
### Fixed
- Fixed string truncation error when inserting long strings.
- Fixed querying from large column name.
- Fixed issue with trailing garbled characters in string retrieval.
- Fixed issue with detecting invalid UTF-16 strings coming from server.
- Fixed issues with binding input text, ntext, and image parameters.
## Linux 4.0.6 - 2016-10-25
Linux drivers compiled with PHP 7.0.12 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2.
### Changed
- Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source.
- Compiler C++ 11 is enabled in config file.
### Fixed
- Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING.
- Fixed the issue with invalid UTF-8 strings, those are detected before executing any queries and proper error message is returned.
- Fixed segmentation fault in sqlsrv_fetch_object and sqlsrv_fetch_array function.
## Windows 4.1.4 - 2016-10-25
Windows drivers compiled with PHP 7.0.12 and 7.1 are available. Here is the list of updates:
### Changed
- Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source.
### Fixed
- Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING.
## Linux 4.0.5 - 2016-10-04
Linux drivers compiled with PHP 7.0.11 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2.
### Fixed
- Fixed segmentation fault when calling PDOStatement::getColumnMeta on RedHat 7.2.
- Fixed segmentation fault when fetch mode is set to ATTR_EMULATE_PREPARES on RedHat 7.2.
- Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error.
## Windows 4.1.3 - 2016-10-04
Updated Windows drivers (4.1.3) compiled with PHP 7.0.11 and 7.1.0RC3 are available. Here is the list of updates:
### Fixed
- Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error.
##Linux 4.0.4 - 2016-09-09
Linux drivers compiled with PHP 7.0.10 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2.
### Added
- Added Support for EMULATE_PREPARE feature.
- Added following integer SQL Types constants for cases which function-like SQL types constants cannot be used e.g. type comparison:
SQLSRV constant | Typical SQL Server data type | SQL type identifier
------------ | ----------------------- | ----------------------
SQLSRV_SQLTYPE_DECIMAL | decimal | SQL_DECIMAL
SQLSRV_SQLTYPE_NUMERIC | numeric | SQL_NUMERIC
SQLSRV_SQLTYPE_CHAR | char | SQL_CHAR
SQLSRV_SQLTYPE_NCHAR | nchar | SQL_WCHAR
SQLSRV_SQLTYPE_VARCHAR | varchar | SQL_VARCHAR
SQLSRV_SQLTYPE_NVARCHAR | nvarchar | SQL_WVARCHAR
SQLSRV_SQLTYPE_BINARY | binary | SQL_BINARY
SQLSRV_SQLTYPE_VARBINARY | varbinary | SQL_VARBINARY
Note: These constants should be used in type comparison operations (refer to issue [#87](https://github.com/Microsoft/msphpsql/issues/87) and [#99](https://github.com/Microsoft/msphpsql/issues/99) ), and don't replace the function like constants with similar syntax. For binding parameters you should use the function-like constants, otherwise you'll get an error.
### Fixed
- Fixed undefined symbols at SQL* error when loading the drivers.
- Fixed undefined symbol issues at LocalAlloc and LocalFree on RedHat7.2.
- Fixed [issue #144](https://github.com/Microsoft/msphpsql/issues/144) (floating point exception).
- Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object).
## Windows 4.1.2 - 2016-09-09
Updated Windows drivers (4.1.2) compiled with PHP 7.0.10 are available. Here is the list of updates:
### Added
- Added following integer SQL Types constants for cases which function-like SQL types constants cannot be used e.g. type comparison:
SQLSRV constant | Typical SQL Server data type | SQL type identifier
------------ | ----------------------- | ----------------------
SQLSRV_SQLTYPE_DECIMAL | decimal | SQL_DECIMAL
SQLSRV_SQLTYPE_NUMERIC | numeric | SQL_NUMERIC
SQLSRV_SQLTYPE_CHAR | char | SQL_CHAR
SQLSRV_SQLTYPE_NCHAR | nchar | SQL_WCHAR
SQLSRV_SQLTYPE_VARCHAR | varchar | SQL_VARCHAR
SQLSRV_SQLTYPE_NVARCHAR | nvarchar | SQL_WVARCHAR
SQLSRV_SQLTYPE_BINARY | binary | SQL_BINARY
SQLSRV_SQLTYPE_VARBINARY | varbinary | SQL_VARBINARY
Note: These constants should be used in type comparison operations (refer to issue [#87](https://github.com/Microsoft/msphpsql/issues/87) and [#99](https://github.com/Microsoft/msphpsql/issues/99) ), and don't replace the function like constants with similar syntax. For binding parameters you should use the function-like constants, otherwise you'll get an error.
### Fixed
- Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object).
## Linux 4.0.3 - 2016-08-23
Linux drivers compiled with PHP 7.0.9 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2.
### Fixed
- Fixed data corruption in binding integer parameters.
- Fixed invalid sql_display_size error.
- Fixed issue with invalid statement options.
- Fixed binding bit parameters.
## Windows 4.1.1 - 2016-08-22
Updated Windows drivers(4.1.1) compiled with PHP 7.0.9 are available and include a couple of bug fixes:
### Fixed
- Fixed issue with storing integers in varchar field.
- Fixed issue with invalid connection handler if one connection fails.
- Fixed crash when emulate prepare is on.
## Linux 4.0.2 - 2016-07-29
### Fixed
- The PDO_SQLSRV driver no longer requires PDO to be built as a shared extension.
- Fixed an issue with format specifiers in error messages.
- Fixed a segmentation fault when using buffered cursors.
- Fixed an issue whereby calling sqlsrv_rows_affected on an empty result set would return a null result instead of 0.
- Fixed an issue with error messages when there is an error in sizes in SQLSRV_SQLTYPE_*.
## Windows 4.1.0 - 2016-07-28
### Fixed
- `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example,
`$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);`
If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`.
Note for exceptions:
- When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string.
- When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off.
- Fixed float truncation when using buffered query.
- Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set.
- Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output.
- Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value.
## Linux 4.0.1 - 2016-07-09
### Added
- Added support for PDO_SQLSRV driver on RedHat 7.
###Changed
- Improved handling varchar(MAX).
- Improved handling basic stream operations.
## Linux 4.0.0 - 2016-06-11
### Added
- The early technical preview (ETP) for SQLSRV and PDO_SQLSRV drivers for Linux with basic functionalities is now available. The SQLSRV driver has been built and tested on Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2, and PDO_SQLSRV driver has been built and tested on Ubuntu 15.04, Ubuntu 16.04.

View file

@ -1,105 +0,0 @@
# Download base image ubuntu 18.04
FROM ubuntu:18.04
# Update Ubuntu Software repository
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \
apt-get install -y software-properties-common && \
add-apt-repository ppa:ondrej/php -y && \
apt-get -y install \
apt-transport-https \
apt-utils \
autoconf \
curl \
libcurl4 \
g++ \
gcc \
git \
lcov \
libxml2-dev \
locales \
make \
php7.3 \
php7.3-dev \
php7.3-intl \
python-pip \
re2c \
unixodbc-dev \
unzip && apt-get clean && \
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \
curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list > /etc/apt/sources.list.d/mssql-release.list && \
apt-get -y update && \
export ACCEPT_EULA=Y && apt-get -y install msodbcsql17 mssql-tools && \
update-alternatives --set php /usr/bin/php7.3
ARG PHPSQLDIR=/REPO/msphpsql-dev
ENV TEST_PHP_SQL_SERVER sql
ENV TEST_PHP_SQL_UID sa
ENV TEST_PHP_SQL_PWD Password123
# update PATH after ODBC driver and tools are installed
ENV PATH="/opt/mssql-tools/bin:${PATH}"
# add locales for testing
RUN sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen
RUN sed -i 's/# fr_FR@euro ISO-8859-15/fr_FR@euro ISO-8859-15/g' /etc/locale.gen
RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen
RUN sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen
RUN sed -i 's/# zh_CN GB2312/zh_CN GB2312/g' /etc/locale.gen
RUN sed -i 's/# zh_CN.GB18030 GB18030/zh_CN.GB18030 GB18030/g' /etc/locale.gen
RUN locale-gen
# set locale to utf-8
# RUN locale-gen en_US.UTF-8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
# install coveralls (upgrade both pip and requests first)
RUN python -m pip install --upgrade pip
RUN python -m pip install --upgrade requests
RUN python -m 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.
#another option is to copy source to build directory on image
RUN mkdir -p $PHPSQLDIR
COPY . $PHPSQLDIR
WORKDIR $PHPSQLDIR/source/
RUN chmod +x ./packagize.sh
RUN /bin/bash -c "./packagize.sh"
RUN echo "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini
RUN echo "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini
# create a writable ini file for testing locales
RUN echo '' > `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/99-overrides.ini
RUN chmod 666 `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/99-overrides.ini
WORKDIR $PHPSQLDIR/source/sqlsrv
RUN /usr/bin/phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install
WORKDIR $PHPSQLDIR/source/pdo_sqlsrv
RUN /usr/bin/phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install
RUN phpenmod sqlsrv pdo_sqlsrv
RUN php --ri sqlsrv && php --ri pdo_sqlsrv
# set name of sql server host to use
WORKDIR $PHPSQLDIR/test/functional/pdo_sqlsrv
RUN sed -i -e 's/TARGET_SERVER/sql/g' MsSetup.inc
RUN sed -i -e 's/TARGET_DATABASE/msphpsql_pdosqlsrv/g' MsSetup.inc
RUN sed -i -e 's/TARGET_USERNAME/'"$TEST_PHP_SQL_UID"'/g' MsSetup.inc
RUN sed -i -e 's/TARGET_PASSWORD/'"$TEST_PHP_SQL_PWD"'/g' MsSetup.inc
WORKDIR $PHPSQLDIR/test/functional/sqlsrv
RUN sed -i -e 's/TARGET_SERVER/sql/g' MsSetup.inc
RUN sed -i -e 's/TARGET_DATABASE/msphpsql_sqlsrv/g' MsSetup.inc
RUN sed -i -e 's/TARGET_USERNAME/'"$TEST_PHP_SQL_UID"'/g' MsSetup.inc
RUN sed -i -e 's/TARGET_PASSWORD/'"$TEST_PHP_SQL_PWD"'/g' MsSetup.inc
WORKDIR $PHPSQLDIR
RUN chmod +x ./entrypoint.sh
CMD /bin/bash ./entrypoint.sh
ENV REPORT_EXIT_STATUS 1
ENV TEST_PHP_EXECUTABLE /usr/bin/php

View file

@ -1,538 +0,0 @@
# Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server
The following instructions assume a clean environment and show how to install PHP 8.1, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu, RedHat, Debian, Suse, Alpine, and macOS. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup).
The following instructions install PHP 8.1 by default using `pecl install`, if the PHP 8.1 packages are available. You may need to run `pecl channel-update pecl.php.net` first. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.4 or 8.0 instead.
Also included are instructions for installing the PHP FastCGI Process Manager, PHP-FPM, on Ubuntu. This is needed if you are using the nginx web server instead of Apache.
While these instructions contain commands to install both SQLSRV and PDO_SQLSRV drivers, the drivers can be installed and function independently. Users comfortable with customizing their configuration can adjust these instructions to be specific to SQLSRV or PDO_SQLSRV. Both drivers have the same dependencies except where noted below.
## Contents of this page
- [Installing the drivers on Ubuntu](#installing-the-drivers-on-ubuntu)
- [Installing the drivers with PHP-FPM on Ubuntu](#installing-the-drivers-with-php-fpm-on-ubuntu)
- [Installing the drivers on Red Hat](#installing-the-drivers-on-red-hat)
- [Installing the drivers on Debian](#installing-the-drivers-on-debian)
- [Installing the drivers on Suse](#installing-the-drivers-on-suse)
- [Installing the drivers on Alpine](#installing-the-drivers-on-alpine)
- [Installing the drivers on macOS](#installing-the-drivers-on-macos)
## Installing the drivers on Ubuntu
> [!NOTE]
> To install PHP 7.4 or 8.0, replace 8.1 with 7.4 or 8.0 in the following commands.
### Step 1. Install PHP
```bash
sudo su
add-apt-repository ppa:ondrej/php -y
apt-get update
apt-get install php8.1 php8.1-dev php8.1-xml -y --allow-unauthenticated
```
### Step 2. Install prerequisites
Install the ODBC driver for Ubuntu by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (Linux)](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15).
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo su
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/8.1/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/8.1/mods-available/pdo_sqlsrv.ini
exit
sudo phpenmod -v 8.1 sqlsrv pdo_sqlsrv
```
If there is only one PHP version in the system, then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`.
### Step 4. Install Apache and configure driver loading
```bash
sudo su
apt-get install libapache2-mod-php8.1 apache2
a2dismod mpm_event
a2enmod mpm_prefork
a2enmod php8.1
exit
```
### Step 5. Restart Apache and test the sample script
```bash
sudo service apache2 restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers with PHP-FPM on Ubuntu
> [!NOTE]
> To install PHP 7.4 or 8.0, replace 8.1 with 7.4 or 8.0 in the following commands.
### Step 1. Install PHP
```bash
sudo su
add-apt-repository ppa:ondrej/php -y
apt-get update
apt-get install php8.1 php8.1-dev php8.1-fpm php8.1-xml -y --allow-unauthenticated
```
Verify the status of the PHP-FPM service by running
```bash
systemctl status php8.1-fpm
```
### Step 2. Install prerequisites
Install the ODBC driver for Ubuntu by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (Linux)](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15).
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
sudo pecl config-set php_ini /etc/php/8.1/fpm/php.ini
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo su
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/8.1/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/8.1/mods-available/pdo_sqlsrv.ini
exit
sudo phpenmod -v 8.1 sqlsrv pdo_sqlsrv
```
If there is only one PHP version in the system, then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`.
Verify that `sqlsrv.ini` and `pdo_sqlsrv.ini` are located in `/etc/php/8.1/fpm/conf.d/`:
```bash
ls /etc/php/8.1/fpm/conf.d/*sqlsrv.ini
```
Restart the PHP-FPM service:
```bash
sudo systemctl restart php8.1-fpm
```
### Step 4. Install and configure nginx
```bash
sudo apt-get update
sudo apt-get install nginx
sudo systemctl status nginx
```
To configure nginx, you must edit the `/etc/nginx/sites-available/default` file. Add `index.php` to the list below the section that says `# Add index.php to the list if you are using PHP`:
```
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html index.php;
```
Next, uncomment and modify the section following `# pass PHP scripts to FastCGI server` as follows:
```
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
```
### Step 5. Restart nginx and test the sample script
```bash
sudo systemctl restart nginx.service
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Red Hat
### Step 1. Install PHP
To install PHP on Red Hat 7, run the following:
> [!NOTE]
> To install PHP 7.4 or 8.0, replace remi-php81 with remi-php74 or remi-php80 respectively in the following commands.
```bash
sudo su
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
subscription-manager repos --enable=rhel-7-server-optional-rpms
yum install yum-utils
yum-config-manager --enable remi-php81
yum update
# Note: The php-pdo package is required only for the PDO_SQLSRV driver
yum install php php-pdo php-pear php-devel
```
To install PHP on Red Hat 8, run the following:
> [!NOTE]
> To install PHP 7.4 or 8.0, replace remi-8.1 with remi-7.4 or remi-8.0 respectively in the following commands.
```bash
sudo su
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf install yum-utils
dnf module reset php
dnf module install php:remi-8.1
subscription-manager repos --enable codeready-builder-for-rhel-8-x86_64-rpms
dnf update
# Note: The php-pdo package is required only for the PDO_SQLSRV driver
dnf install php-pdo php-pear php-devel
```
### Step 2. Install prerequisites
Install the ODBC driver for Red Hat 7 or 8 by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (Linux)](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15).
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
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
```
You can alternatively install from the Remi repo:
```bash
sudo yum install php-sqlsrv
```
### Step 4. Install Apache
```bash
sudo yum install httpd
```
SELinux is installed by default and runs in Enforcing mode. To allow Apache to connect to databases through SELinux, run the following command:
```bash
sudo setsebool -P httpd_can_network_connect_db 1
```
### Step 5. Restart Apache and test the sample script
```bash
sudo apachectl restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Debian
> [!NOTE]
> To install PHP 7.4 or 8.0, replace 8.1 in the following commands with 7.4 or 8.0.
### Step 1. Install PHP
```bash
sudo su
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 php8.1 php8.1-dev php8.1-xml php8.1-intl
```
### Step 2. Install prerequisites
Install the ODBC driver for Debian by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (Linux)](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15).
You may also need to generate the correct locale to get PHP output to display correctly in a browser. For example, for the en_US UTF-8 locale, run the following commands:
```bash
sudo su
sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen
locale-gen
```
You may need to add `/usr/sbin` to your `$PATH`, as the `locale-gen` executable is located there.
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
sudo su
printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/8.1/mods-available/sqlsrv.ini
printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/8.1/mods-available/pdo_sqlsrv.ini
exit
sudo phpenmod -v 8.1 sqlsrv pdo_sqlsrv
```
If there is only one PHP version in the system, then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. As with `locale-gen`, `phpenmod` is located in `/usr/sbin` so you may need to add this directory to your `$PATH`.
### Step 4. Install Apache and configure driver loading
```bash
sudo su
apt-get install libapache2-mod-php8.1 apache2
a2dismod mpm_event
a2enmod mpm_prefork
a2enmod php8.1
```
### Step 5. Restart Apache and test the sample script
```bash
sudo service apache2 restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Suse
> [!NOTE]
> In the following instructions, replace `<SuseVersion>` with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15_SP3 or SLE_15_SP4 (or above). For Suse 12, use SLE_12_SP5 (or above). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or check `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse.
> [!NOTE]
> Packages for PHP 7.4 or above are not available for Suse 12, as of today.
### Step 1. Install PHP
```bash
sudo su
zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php/<SuseVersion>/devel:languages:php.repo
zypper --gpg-auto-import-keys refresh
zypper -n install php8 php8-pdo php8-devel php8-openssl
```
### Step 2. Install prerequisites
Install the ODBC driver for Suse by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (Linux)](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15).
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
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
```
### Step 4. Install Apache and configure driver loading
```bash
sudo su
zypper install apache2 apache2-mod_php8
a2enmod php8
echo "extension=sqlsrv.so" >> /etc/php8/apache2/php.ini
echo "extension=pdo_sqlsrv.so" >> /etc/php8/apache2/php.ini
exit
```
### Step 5. Restart Apache and test the sample script
```bash
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 Alpine
> [!NOTE]
> PHP 8.1 or above may be available from testing or edge repositories for Alpine. You can instead compile PHP from source.
### Step 1. Install PHP
PHP packages for Alpine can be found in the `edge/community` repository. Please check [Enable Community Repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) on their WIKI page. Add the following line to `/etc/apk/repositories`, replacing `<mirror>` with the URL of an Alpine repository mirror:
```bash
http://<mirror>/alpine/edge/community
```
Then run:
```bash
sudo su
apk update
# Note: The php*-pdo package is required only for the PDO_SQLSRV driver
# For PHP 7.*
apk add php7 php7-dev php7-pear php7-pdo php7-openssl autoconf make g++
# For PHP 8.*
apk add php8 php8-dev php8-pear php8-pdo php8-openssl autoconf make g++
# The following symbolic links are optional but useful
ln -s /usr/bin/php8 /usr/bin/php
ln -s /usr/bin/phpize8 /usr/bin/phpize
ln -s /usr/bin/pecl8 /usr/bin/pecl
ln -s /usr/bin/php-config8 /usr/bin/php-config
```
### Step 2. Install prerequisites
Install the ODBC driver for Alpine by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (Linux)](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15).
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
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*||"`/10_pdo_sqlsrv.ini
echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20_sqlsrv.ini
```
### Step 4. Install Apache and configure driver loading
```bash
# For PHP 7.*
sudo apk add php7-apache2 apache2
# For PHP 8.*
sudo apk add php8-apache2 apache2
```
### Step 5. Restart Apache and test the sample script
```bash
sudo rc-service apache2 restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on macOS
If you do not already have it, install Homebrew as follows:
```bash
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
> [!NOTE]
> If using Apple M1 ARM64 hardware, please install Homebrew and PHP directly without using the emulator Rosetta 2.
> [!NOTE]
> To install PHP 7.4 or 8.0, replace php@8.1 with php@7.4 or php@8.0 respectively in the following commands.
### Step 1. Install PHP
```bash
brew tap
brew tap homebrew/core
brew install php@8.1
```
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:
```bash
brew link --force --overwrite php@8.1
```
If using Apple M1 ARM64, you might need to set the path:
```bash
export PATH="/opt/homebrew/bin:$PATH"
```
### Step 2. Install prerequisites
Install the ODBC driver for macOS by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (macOS)](
https://docs.microsoft.com/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15).
> [!NOTE]
> If using Apple M1 ARM64 hardware, please install Microsoft ODBC driver 17.8+ directly without using the emulator Rosetta 2.
In addition, you may need to install the GNU make tools:
```bash
brew install autoconf automake libtool
```
### Step 3. Install the PHP drivers for Microsoft SQL Server
```bash
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
If using Apple M1 ARM64, do the following instead:
```bash
sudo CXXFLAGS="-I/opt/homebrew/opt/unixodbc/include/" LDFLAGS="-L/opt/homebrew/lib/" pecl install sqlsrv
sudo CXXFLAGS="-I/opt/homebrew/opt/unixodbc/include/" LDFLAGS="-L/opt/homebrew/lib/" pecl install pdo_sqlsrv
```
### Step 4. Install Apache and configure driver loading
> [!NOTE]
> The latest macOS 11.0 Big Sur comes with Apache 2.4 pre-installed, but Apple has also removed some required scripts. The solution is to install Apache 2.4 via Homebrew and then configure it, but this is out of scope for this installation guide, so please check Apache or Homebrew for detailed instructions.
```bash
brew install apache2
```
To find the Apache configuration file, `httpd.conf`, for your Apache installation, run
```bash
/usr/local/bin/apachectl -V | grep SERVER_CONFIG_FILE
```
The following commands append the required configuration to `httpd.conf`. Be sure to substitute the path returned by the preceding command in place of `/usr/local/etc/httpd/httpd.conf`:
```bash
echo "LoadModule php7_module /usr/local/opt/php@8.1/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
```bash
sudo apachectl restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Testing Your Installation
To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, `/var/www/localhost/htdocs` on Alpine, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate.
### SQLSRV example
```php
<?php
$serverName = "yourServername";
$connectionOptions = array(
"database" => "yourDatabase",
"uid" => "yourUsername",
"pwd" => "yourPassword"
);
function exception_handler($exception) {
echo "<h1>Failure</h1>";
echo "Uncaught exception: " , $exception->getMessage();
echo "<h1>PHP Info for troubleshooting</h1>";
phpinfo();
}
set_exception_handler('exception_handler');
// Establishes the connection
$conn = sqlsrv_connect($serverName, $connectionOptions);
if ($conn === false) {
die(formatErrors(sqlsrv_errors()));
}
// Select Query
$tsql = "SELECT @@Version AS SQL_VERSION";
// Executes the query
$stmt = sqlsrv_query($conn, $tsql);
// Error handling
if ($stmt === false) {
die(formatErrors(sqlsrv_errors()));
}
?>
<h1> Success Results : </h1>
<?php
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
echo $row['SQL_VERSION'] . PHP_EOL;
}
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
function formatErrors($errors)
{
// Display errors
echo "<h1>SQL Error:</h1>";
echo "Error information: <br/>";
foreach ($errors as $error) {
echo "SQLSTATE: ". $error['SQLSTATE'] . "<br/>";
echo "Code: ". $error['code'] . "<br/>";
echo "Message: ". $error['message'] . "<br/>";
}
}
?>
```
### PDO_SQLSRV example
```php
<?php
try {
$serverName = "yourServername";
$databaseName = "yourDatabase";
$uid = "yourUsername";
$pwd = "yourPassword";
$conn = new PDO("sqlsrv:server = $serverName; Database = $databaseName;", $uid, $pwd);
// Select Query
$tsql = "SELECT @@Version AS SQL_VERSION";
// Executes the query
$stmt = $conn->query($tsql);
} catch (PDOException $exception1) {
echo "<h1>Caught PDO exception:</h1>";
echo $exception1->getMessage() . PHP_EOL;
echo "<h1>PHP Info for troubleshooting</h1>";
phpinfo();
}
?>
<h1> Success Results : </h1>
<?php
try {
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['SQL_VERSION'] . PHP_EOL;
}
} catch (PDOException $exception2) {
// Display errors
echo "<h1>Caught PDO exception:</h1>";
echo $exception2->getMessage() . PHP_EOL;
}
unset($stmt);
unset($conn);
?>
```
Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. If you don't see a success message showing SQL version information, you can do some basic troubleshooting by running the script from the command line:
```bash
php testsql.php
```
If running from the command line is successful but nothing shows in your browser, check the [Apache log files](https://linuxize.com/post/apache-log-files/#location-of-the-log-files). For additional help, see [Support resources](support-resources-for-the-php-sql-driver.md) for places to go.

140
README.md
View file

@ -1,140 +0,0 @@
# Microsoft Drivers for PHP for Microsoft SQL Server
**Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server**
The [Microsoft Drivers for PHP for Microsoft SQL Server][phpdoc] 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 2012 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server.
This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.3+ with improvements on both drivers and some limitations. Upcoming [releases][releases] will contain additional functionalities, bug fixes, and more.
## Take our survey
Thank you for taking the time to participate in the [sentiment survey](https://github.com/microsoft/msphpsql/wiki/Survey-Results). You can continue to help us improve by letting us know how we are doing and how you use [PHP][phpweb]:
<a href="https://aka.ms/mssqlphpsurvey"><img style="float: right;" height="67" width="156" src="https://sqlchoice.blob.core.windows.net/sqlchoice/static/images/survey.png"></a>
### Status of Most Recent Builds
| Azure Pipelines (Linux) | AppVeyor (Windows) | Coverage (Windows) |
|--------------------------|--------------------------|---------------------------------------|
| [![az-image][]][az-site] | [![av-image][]][av-site] | [![Coverage Codecov][]][codecov-site] |
[av-image]: https://ci.appveyor.com/api/projects/status/vo4rfei6lxlamrnc?svg=true
[av-site]: https://ci.appveyor.com/project/msphpsql/msphpsql/branch/dev
[az-site]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_build/latest?definitionId=6&branchName=dev
[az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev
[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg
[codecov-site]: https://codecov.io/gh/microsoft/msphpsql
## Get Started
Please follow the [Getting started](https://docs.microsoft.com/sql/connect/php/getting-started-with-the-php-sql-driver) page.
## Announcements
Please follow [SQL Server Drivers][sqldrivers] for announcements.
## Prerequisites
For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs.
On the client machine:
- 7.4.x, 8.0.x, 8.1.x
- [Microsoft ODBC Driver 17 or Microsoft ODBC Driver 13][odbcdoc]
- If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP
On the server side, Microsoft SQL Server 2012 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux.
## Building and Installing the Drivers on Windows
The drivers are distributed as pre-compiled extensions for PHP found on the [releases page][releases]. They are available in thread-safe and non-thread-safe versions, and in 32-bit (Windows only) and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread-safe versions. The thread safety configuration of your web server will determine which version you need.
If you choose to build the drivers, you must be able to build PHP 7.* or 8.* 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/blob/master/buildscripts/README.md) -- 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 the ini file. 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/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs.
Finally, if running PHP in a Web server, restart the Web server.
## Install (UNIX)
For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs][unixinstructions].
## Sample Code
For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver). For information on how to use the driver, see [Microsoft Drivers for PHP for Microsoft SQL Server][phpdoc].
## Limitations and Known Issues
Please refer to [Releases][releases] for the latest limitations and known issues.
## Version number
The version numbers of the PHP drivers follow [semantic versioning](https://semver.org/):
Given a version number MAJOR.MINOR.PATCH,
- MAJOR version is incremented when an incompatible API change is made,
- MINOR version is incremented when functionality is added in a backwards-compatible manner, and
- PATCH version is incremented when backwards-compatible bug fixes are made.
The version number may have trailing pre-release version identifiers to indicate the stability and/or build metadata.
- Pre-release version is denoted by a hyphen followed by `beta` or `RC` followed by a number. Production quality releases do not contain the pre-release version. `beta` has lower precedence than `RC`. Note that the PECL package version numbers do not have the hyphen before the pre-release version, owing to restrictions in PECL. An example of a PECL package version is `5.9.0beta2`.
- Build metadata may be denoted by a plus sign followed by a number of digits, such as `5.9.0-beta2+13930`. Build metadata does not affect the precedence order.
## Future Plans
- Expand SQL Server feature support (example: Azure Active Directory, Always Encrypted, etc.)
- Add more verification/fundamental tests
- Improve performance
- Bug fixes
## 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) for common problems
- Report each issue as a new issue (but check first if it's already been reported)
- 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!
## Questions
**Q:** Can we get dates for any of the Future Plans listed above?
**A:** At this time, Microsoft is not able to announce dates. We are working hard to release future versions of the driver and will share future plans as appropriate.
**Q:** What's next?
**A:** We will continue working on our future plans and releasing previews of upcoming [releases][releases]
**Q:** Is Microsoft taking pull requests for this project?
**A:** Yes. Please submit pull requests to the **dev** branch, 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.
## Code of conduct
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
## Resources
**Documentation**: [Microsoft Docs Online][phpdoc].
**SQL Server Drivers**: Please browse the articles for announcements of various [SQL Server Drivers][sqldrivers].
**Known Issues**: Please visit the [project on Github][project] to view outstanding [issues][issues] and report new ones.
[sqldrivers]: https://techcommunity.microsoft.com/t5/SQL-Server/bg-p/SQLServer/label-name/SQLServerDrivers
[project]: https://github.com/microsoft/msphpsql
[issues]: https://github.com/microsoft/msphpsql/issues
[releases]: https://github.com/microsoft/msphpsql/releases
[phpweb]: https://php.net
[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2
[phpdoc]: https://docs.microsoft.com/sql/connect/php/microsoft-php-driver-for-sql-server
[odbcdoc]: https://docs.microsoft.com/sql/connect/odbc/microsoft-odbc-driver-for-sql-server
[unixinstructions]: https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac

View file

@ -1,173 +0,0 @@
version: '{branch}.{build}'
branches:
except:
- PHP-7.0-Linux
- PHP5
environment:
# MSSQL credentials from https://www.appveyor.com/docs/services-databases/
TEST_PHP_SQL_PWD: Password12!
TEST_PHP_SQL_UID: sa
SQLSRV_DBNAME: msphpsql_sqlsrv
PDOSQLSRV_DBNAME: msphpsql_pdosqlsrv
PYTHON: c:\Python36
# For details about Appveyor build worker images (VM template): https://www.appveyor.com/docs/build-environment/#build-worker-images
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
BUILD_PLATFORM: x64
TEST_PHP_SQL_SERVER: (local)\SQL2017
SQL_INSTANCE: SQL2017
PHP_VC: vc15
PHP_MAJOR_VER: 7.4
PHP_MINOR_VER: latest
PHP_EXE_PATH: x64\Release_TS
THREAD: ts
platform: x64
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
BUILD_PLATFORM: x86
TEST_PHP_SQL_SERVER: (local)\SQL2019
SQL_INSTANCE: SQL2019
PHP_VC: vs16
PHP_MAJOR_VER: 8.0
PHP_MINOR_VER: latest
PHP_EXE_PATH: Release
THREAD: nts
platform: x86
# PHP_MAJOR_VER is PHP major version to build (7.4, 7.3)
# PHP_MINOR_VER is PHP point release number (or latest for latest release)
# PHP_VC is the Visual C++ version
# PHP_EXE_PATH is the relative path from php src folder to php executable
# THREAD is either non-thread-safe (nts) or thread-safe (ts)
matrix:
fast_finish: true
# clone directory (or %APPVEYOR_BUILD_FOLDER%)
clone_folder: c:\projects\sqlphp
build:
parallel: true # enable MSBuild parallel builds
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;
$instanceName = $env:SQL_INSTANCE;
$uri = "ManagedComputer[@Name='$env:COMPUTERNAME']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']";
$wmi = New-Object ('Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer');
$tcp = $wmi.GetSmoObject($uri);
$tcp.IsEnabled = $true;
$tcp.Alter();
Start-Service "MSSQL`$$instanceName";
Set-Service SQLBrowser -StartupType Manual;
Start-Service SQLBrowser;
- echo Downloading prerequisites
- ps: |
$client = New-Object Net.WebClient;
$client.Headers.Add("user-agent", "appveyor-ci-build2");
$client.DownloadFile("http://windows.php.net/downloads/releases/sha256sum.txt", "c:\projects\sha256sum.txt");
If ($env:PHP_MINOR_VER -Match "latest") {
$env:PHP_VERSION=type c:\projects\sha256sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } | Select -First 1 ;
} Else {
$env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER;
}
- echo Downloading MSODBCSQL 17
# AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it
- ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/a/e/b/aeb7d4ff-ca20-45db-86b8-8a8f774ce97b/en-US/17.8.1.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL
- echo Checking the version of MSODBCSQL
- reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server"
- dir %WINDIR%\System32\msodbcsql*.dll
- cd c:\projects
- 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: 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: 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;%PYTHON%;%PYTHON%\Scripts;%path%
- copy %APPVEYOR_BUILD_FOLDER%\codecov.yml c:\projects
build_script:
- 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\%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:
- 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%
# for code coverage - exclude the following files:
# core_init.cpp, which primarily consists module initialization and shutdown
# core_stream.cpp for pdo_sqlsrv because it is only used by sqlsrv driver
- ps: >-
If ($env:BUILD_PLATFORM -Match "x86") {
Write-Host "Running phpt tests via OpenCppCoverage..."
OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources core_init.cpp --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P --no-color ${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;
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;
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_EXE_PATH%
- ps: $fileExists = Test-Path "c:\projects\coverage.xml"
- ps: >-
If ($fileExists -eq $true) {
cd c:\projects
Write-Host "Running coverage analysis...";
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:
- cd %APPVEYOR_BUILD_FOLDER%\test\functional\
- ps: ls *.log
- python output.py
- ps: $difffiles = Get-ChildItem sqlsrv\*.diff
- ps: $outfiles = Get-ChildItem sqlsrv\*.out
- ps: foreach($file in $difffiles){ls $file; more $file}
- ps: foreach($file in $outfiles){ls $file; more $file}
- ps: $diff2files = Get-ChildItem pdo_sqlsrv\*.diff
- ps: $out2files = Get-ChildItem pdo_sqlsrv\*.out
- ps: foreach($file in $diff2files){ls $file; more $file}
- ps: foreach($file in $out2files){ls $file; more $file}
- cd %APPVEYOR_BUILD_FOLDER%\test\functional\
- ps: $xmlfiles = Get-ChildItem *.xml
- ps: foreach($file in $xmlfiles){(new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $file))}
- ps: >-
If ($difffiles -ne $null -Or $diff2files -ne $null) {
$host.SetShouldExit(1);
Write-Host "Forcing build failure due to phpt unit test failure(s)";
}

View file

@ -1,347 +0,0 @@
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
variables:
server: 'localhost,1433'
host: 'sql1'
sqlsrv_db: 'sqlsrv_testdb'
pdo_sqlsrv_db: 'pdo_sqlsrv_testdb'
uid: 'sa'
pwd: 'Password456!'
trigger:
- dev
pr:
- dev
jobs:
- job: macOS
pool:
vmImage: 'macOS-10.15'
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
architecture: 'x64'
- script: |
brew tap
brew tap homebrew/core
brew reinstall autoconf automake libtool
php -v
displayName: 'Install PHP prerequisites'
- script: |
echo ready to build extensions
cd $(Build.SourcesDirectory)/source
chmod a+x packagize.sh
./packagize.sh
cd $(Build.SourcesDirectory)/source/sqlsrv
ls -al
phpize && ./configure && make && sudo make install
cp run-tests.php $(Build.SourcesDirectory)/test/functional/sqlsrv
cd $(Build.SourcesDirectory)/source/pdo_sqlsrv
ls -al
phpize && ./configure && make && sudo make install
cp run-tests.php $(Build.SourcesDirectory)/test/functional/pdo_sqlsrv
echo extension=pdo_sqlsrv.so >> `php --ini | grep "Loaded Configuration File" | sed -e "s|.*:\s*||"`
echo extension=sqlsrv.so >> `php --ini | grep "Loaded Configuration File" | sed -e "s|.*:\s*||"`
php --ri sqlsrv
php --ri pdo_sqlsrv
displayName: 'Build and install drivers'
- job: Linux
variables:
phpver: 8.0
pool:
vmImage: 'ubuntu-20.04'
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
architecture: 'x64'
- script: |
sudo update-alternatives --set php /usr/bin/php$(phpver)
sudo update-alternatives --set phpize /usr/bin/phpize$(phpver)
sudo update-alternatives --set phar /usr/bin/phar$(phpver)
sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpver)
sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpver)
sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpver)
sudo update-alternatives --set php-config /usr/bin/php-config$(phpver)
sudo phpdismod xdebug
php -version
displayName: 'Use PHP version $(phpver)'
- script: |
echo install ODBC and dependencies
sudo apt-get purge unixodbc
sudo apt autoremove
sudo curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > mssql-release.list
sudo mv mssql-release.list /etc/apt/sources.list.d/
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install msodbcsql17 mssql-tools
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc
sudo apt-get install unixodbc-dev
odbcinst --j
odbcinst -q -d -n "ODBC Driver 17 for SQL Server"
displayName: 'Install prerequisites'
- script: |
python -m pip install --upgrade pip
python -m pip install --upgrade requests
python -m pip install PyYAML
python -m pip install cpp-coveralls
displayName: 'Install coveralls (upgrade both pip and requests first)'
- script: |
docker pull mcr.microsoft.com/mssql/server:2019-latest
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=$(pwd)' -p 1433:1433 -h $(host) --name=$(host) -d mcr.microsoft.com/mssql/server:2019-latest
docker ps -a
sleep 10
docker exec -t $(host) /opt/mssql-tools/bin/sqlcmd -S $(server) -U $(uid) -P $(pwd) -Q 'select @@Version'
displayName: 'Run SQL Server for Linux'
- script: |
sudo sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen
sudo sed -i 's/# fr_FR@euro ISO-8859-15/fr_FR@euro ISO-8859-15/g' /etc/locale.gen
sudo sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen
sudo sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen
sudo sed -i 's/# zh_CN GB2312/zh_CN GB2312/g' /etc/locale.gen
sudo sed -i 's/# zh_CN.GB18030 GB18030/zh_CN.GB18030 GB18030/g' /etc/locale.gen
sudo locale-gen
export LANG='en_US.UTF-8'
export LANGUAGE='en_US:en'
export LC_ALL='en_US.UTF-8'
displayName: 'Generate locales for testing'
- script: |
echo setting env variables
export TEST_PHP_SQL_SERVER='$(server)'
export TEST_PHP_SQL_UID='$(uid)'
export TEST_PHP_SQL_PWD='$(pwd)'
cd $(Build.SourcesDirectory)/test/functional/setup
python ./setup_dbs.py -dbname $(sqlsrv_db)
python ./setup_dbs.py -dbname $(pdo_sqlsrv_db)
displayName: 'Set up test databases'
- script: |
echo ready to build extensions
sudo apt-get install -y php$(phpver)-intl
cd $(Build.SourcesDirectory)/source
chmod a+x packagize.sh
./packagize.sh
dest=`php --ini | grep "Scan for additional .ini files" | sudo sed -e "s|.*:\s*||"`/
cd $(Build.SourcesDirectory)/source/sqlsrv
ls -al
phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && sudo make install
cp run-tests.php $(Build.SourcesDirectory)/test/functional/sqlsrv
echo extension=sqlsrv.so >> 20-sqlsrv.ini
echo copying sqlsrv to $dest
sudo cp 20-sqlsrv.ini $dest
cd $(Build.SourcesDirectory)/source/pdo_sqlsrv
ls -al
phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && sudo make install
cp run-tests.php $(Build.SourcesDirectory)/test/functional/pdo_sqlsrv
echo extension=pdo_sqlsrv.so >> 30-pdo_sqlsrv.ini
echo copying pdo_sqlsrv to $dest
sudo cp 30-pdo_sqlsrv.ini $dest
sudo touch $dest/99-overrides.ini
sudo chmod 666 $dest/99-overrides.ini
php --ri sqlsrv
php --ri pdo_sqlsrv
displayName: 'Build and install drivers'
- script: |
cd $(Build.SourcesDirectory)/test/functional/sqlsrv
sed -i -e 's/TARGET_SERVER/'"$(server)"'/g' MsSetup.inc
sed -i -e 's/TARGET_DATABASE/'"$(sqlsrv_db)"'/g' MsSetup.inc
sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc
sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc
export LC_ALL='en_US.UTF-8'
php run-tests.php -P ./*.phpt --no-color 2>&1 | tee ../sqlsrv.log
displayName: 'Run sqlsrv functional tests'
- script: |
cd $(Build.SourcesDirectory)/test/functional/pdo_sqlsrv
sed -i -e 's/TARGET_SERVER/'"$(server)"'/g' MsSetup.inc
sed -i -e 's/TARGET_DATABASE/'"$(pdo_sqlsrv_db)"'/g' MsSetup.inc
sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc
sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc
export LC_ALL='en_US.UTF-8'
php run-tests.php -P ./*.phpt --no-color 2>&1 | tee ../pdo_sqlsrv.log
displayName: 'Run pdo_sqlsrv functional tests'
- script: |
cd $(Build.SourcesDirectory)
echo -e "service_name: Azure Pipelines\n" > .coveralls.yml
coveralls -i ./source/ -e ./source/shared/ -e ./test/ -e ./source/pdo_sqlsrv/shared/core_stream.cpp \
-E r'.*localization*' -E r'.*globalization*' --gcov-options '\-lp'
displayName: 'Invoke coveralls using repo token'
condition: false
env:
COVERALLS_REPO_TOKEN: $(repo_token)
TRAVIS_JOB_ID: $(Build.BuildId)
TRAVIS_BRANCH: $(Build.SourceBranchName)
PYTHONWARNINGS: ignore::yaml.YAMLLoadWarning
- script: |
cd $(Build.SourcesDirectory)/test/functional/
for f in sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; echo ''; done || true
for f in pdo_sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; echo ''; done || true
python output.py
ls -l *.xml
displayName: 'Processing test results'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
failTaskOnFailedTests: true
searchFolder: '$(Build.SourcesDirectory)/test/functional/'
- script: |
docker stop $(host)
docker rm $(host)
displayName: 'Stop SQL Server for Linux'
condition: always()
- job: Windows
variables:
phpVersion: 8.1
pool:
vmImage: 'windows-2019'
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
architecture: 'x64'
- script: |
dir C:\tools\php\php*
dir C:\tools\php\ext\
echo extension_dir=C:\tools\php\ext >> C:\tools\php\php.ini
php --ini
php -v
displayName: 'Check PHP'
- powershell: |
cd $(Build.SourcesDirectory)\test\functional\sqlsrv
(Get-Content .\MsSetup.inc) | ForEach-Object { $_ -replace "TARGET_SERVER", "$(host)" -replace "TARGET_DATABASE", "$(sqlsrv_db)" -replace "TARGET_USERNAME", "$(uid)" -replace "TARGET_PASSWORD", "$(pwd)" } | Set-Content .\MsSetup.inc
Select-String $(host) .\MsSetup.inc
Select-String $(sqlsrv_db) .\MsSetup.inc
cd $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv
(Get-Content .\MsSetup.inc) | ForEach-Object { $_ -replace "TARGET_SERVER", "$(host)" -replace "TARGET_DATABASE", "$(pdo_sqlsrv_db)" -replace "TARGET_USERNAME", "$(uid)" -replace "TARGET_PASSWORD", "$(pwd)" } | Set-Content .\MsSetup.inc
Select-String $(host) .\MsSetup.inc
Select-String $(pdo_sqlsrv_db) .\MsSetup.inc
displayName: 'Update connection credentials'
condition: false
- powershell: |
$client = New-Object Net.WebClient
$client.DownloadFile('https://download.microsoft.com/download/a/e/b/aeb7d4ff-ca20-45db-86b8-8a8f774ce97b/en-US/17.8.1.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi')
$client.DownloadFile('https://download.microsoft.com/download/0/e/6/0e63d835-3513-45a0-9cf0-0bc75fb4269e/EN/x64/MsSqlCmdLnUtils.msi', 'MsSqlCmdLnUtils.msi')
dir *.msi
displayName: 'Download ODBC msi and sql tools msi'
condition: false
- script: |
msiexec /i "msodbcsql.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL
reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server"
dir %WINDIR%\System32\msodbcsql*.dll
displayName: 'Install ODBC driver'
condition: false
# FOR SOME REASON the set up did not set the PATH
- script: |
msiexec /i "MsSqlCmdLnUtils.msi" /qn IACCEPTMSSQLCMDLNUTILSLICENSETERMS=YES
displayName: 'Install SQL command line utilities version 15'
condition: false
- powershell: |
$client = New-Object Net.WebClient
$client.Headers.Add("user-agent", "azure pipeline build")
$client.DownloadFile("http://windows.php.net/downloads/releases/releases.json", "releases.json");
$jsondata = Get-Content -Path .\releases.json | ConvertFrom-Json
$env:VERSION = $jsondata.{$(phpVersion)}.version
Write-Host "Latest PHP $(phpVersion) is ${env:VERSION}"
cd $(Build.SourcesDirectory)\buildscripts\
python builddrivers.py --PHPVER=${env:VERSION} --DRIVER=sqlsrv --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)\source --TESTING --NO_RENAME
dir *sqlsrv*.dll
python builddrivers.py --PHPVER=${env:VERSION} --DRIVER=pdo_sqlsrv --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)\source --TESTING --NO_RENAME
cp $(Build.SourcesDirectory)\buildscripts\php-sdk\phpdev\vs16\x64\php-${env:VERSION}-src\run-tests.php $(Build.SourcesDirectory)\test\functional\sqlsrv
cp $(Build.SourcesDirectory)\buildscripts\php-sdk\phpdev\vs16\x64\php-${env:VERSION}-src\run-tests.php $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv
dir *sqlsrv*.dll
cp *sqlsrv*.dll C:\tools\php\ext\
displayName: 'Build drivers (separately) for the latest version of PHP $(phpVersion)'
- script: |
echo extension=php_sqlsrv.dll >> C:\tools\php\php.ini
echo extension=php_pdo_sqlsrv.dll >> C:\tools\php\php.ini
php --ri sqlsrv
php --ri pdo_sqlsrv
displayName: 'Load drivers'
condition: false
- script: |
docker pull microsoft/mssql-server-windows-developer
docker run -d --name sqlcontainer -h $(host) -p 1433:1433 -e sa_password=$(pwd) -e ACCEPT_EULA=Y microsoft/mssql-server-windows-developer
docker ps -a
displayName: 'Run SQL Server for Windows Server'
condition: false
- script: |
set path=C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;%path%
sqlcmd -S $(host) -U $(uid) -P $(pwd) -Q "SELECT @@Version"
set TEST_PHP_SQL_SERVER=$(host)
set TEST_PHP_SQL_UID=$(uid)
set TEST_PHP_SQL_PWD=$(pwd)
cd $(Build.SourcesDirectory)\test\functional\setup
python setup_dbs.py -dbname $(sqlsrv_db)
python setup_dbs.py -dbname $(pdo_sqlsrv_db)
displayName: 'Set up test databases'
condition: false
- script: |
cd $(Build.SourcesDirectory)\test\functional\sqlsrv
php run-tests.php -P sqlsrv_client_info.phpt
cd $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv
php run-tests.php -P pdo_getAttribute_clientInfo.phpt
displayName: 'Smoke testing'
condition: false
- script: |
docker stop sqlcontainer
docker rm sqlcontainer
displayName: 'Stop SQL Server for Windows Server'
condition: false

View file

@ -1,100 +0,0 @@
# Windows
## Prerequisites
To build extensions for
1. PHP 7.0* or PHP 7.1*
* install Visual Studio 2015 and make sure C++ tools are enabled.
2. PHP 7.2* or above
* install Visual Studio 2017 (PHP 7.*) or Visual Studio 2019 (PHP 8.*), including Visual C++ toolset and the Windows SDK components.
To use the sample build scripts `builddrivers.py` and `buildtools.py`, install Python 3.x and Git for Windows (which comes with Visual Studio 2017 or 2019). If `git` is unrecognized in a regular command prompt, make sure the environment path is set up correctly.
## Compile the drivers
You must first be able to build PHP source without including our PHP extensions. For help with building PHP 7.0* or PHP 7.1* in Windows, see the [official PHP website](https://wiki.php.net/internals/windows/stepbystepbuild). For PHP 7.2 or above, visit [PHP SDK page](https://github.com/OSTC/php-sdk-binary-tools) for new instructions.
The Microsoft Drivers for PHP for SQL Server have been compiled and tested with PHP 7.3+ using Visual Studio 2017 and PHP 8.0+ using Visual Studio 2019. The drivers for Windows that are published for each release (including previews) are digitally signed. You are recommended to sign the binaries you have compiled locally for your own development or testing purposes, using tools like Authenticode. It verifies the publisher's identity and prevents malicious actors from posing as legitimate developers.
### Manually building from source
1. Download the *source* directory from this repository
2. Make a copy of the *shared* folder as a subfolder in *sqlsrv* and/or *pdo_sqlsrv* folder
3. Copy the *sqlsrv* and/or *pdo_sqlsrv* folder(s) into the PHP source *ext* subdirectory
4. Run `buildconf --force` to rebuild the configure.js script to include the *sqlsrv* and/or *pdo_sqlsrv* driver(s).
5. Run `configure.bat` with the desired driver options (as shown below) to generate the makefile. You can run `configure.bat --help` to see what other options are available. For example, for non-thread safe build, add this option `--disable-zts`.
* For SQLSRV add: `--enable-sqlsrv=shared`
* For PDO_SQLSRV add: `--enable-pdo --with-pdo-sqlsrv=shared`
6. Run `nmake`. Optionally, you can run `nmake clean` first.
7. To install the drivers, there are two ways:
* Run `nmake install`, or
* Copy the drivers:
* Find the directory where the newly compiled *php.exe* is
* Locate the compiled *php_sqlsrv.dll* and/or *php_pdo_sqlsrv.dll*
* Copy the dll(s) to the *ext* subfolder
### Using the sample build scripts
The sample build scripts, `builddrivers.py` and `buildtools.py`, can be used to build our extensions for PHP in Windows.
#### Overview
When asked to provide the PHP version, you should enter values like `7.4.27`. If it's alpha, beta, or RC version, make sure the name you provide matches the PHP tag name without the prefix `php-`. For example, for PHP 8.0.0 beta 3, the tag name is `php-8.0.0beta3`, so you will enter `8.0.0beta3`. Visit [PHP SRC]( https://github.com/php/php-src) to find the appropriate tag names.
PHP recommends to unzip the PHP SDK into the shortest possible path, preferrably somewhere near the root drive. Therefore, this script will, by default, create a `php-sdk` folder in the C:\ drive, and this `php-sdk` directory tree will remain unless you remove it yourself. For ongoing development, we suggest you keep it around. The build scripts will handle updating the PHP SDK if a new version is available.
#### Steps
1. Launch a regular `cmd` prompt
2. Change to the directory where the Python scripts `builddrivers.py` and `buildtools.py` are
3. Interactive mode:
* Type `py builddrivers.py` to start the interactive mode. Use lower cases to answer the following questions:
* PHP Version
* 64-bit?
* Thread safe?
* Driver?
* Debug enabled?
* Download source from GitHub?
* For `yes/no` questions, you can simply hit `ENTER` key for `yes`. Other questions are self-explanatory.
4. Use Command-line arguments
* Type `py builddrivers.py -h` to get a list of options and their descriptions
* For example,
* `py builddrivers.py --PHPVER=7.4.10 --ARCH=x64 --THREAD=nts --DRIVER=sqlsrv --SOURCE=C:\local\source`
* `py builddrivers.py --PHPVER=8.1.0 --ARCH=x86 --THREAD=ts --DEBUG`
5. Based on the given configuration, if the script detects the presence of the PHP source directory, you can choose whether to rebuild, clean or superclean:
* `rebuild` to build again using the same configuration (32 bit, thread safe, etc.)
* `clean` to remove previous builds (binaries)
* `superclean` to remove the entire `php-<version>-src` directory, which is often unnecessary
6. If you choose not to download from a GitHub repository, you will be asked to provide the full path to your local Source folder.
7. If the compilation is successful, you will be given the option to rebuild or quit.
#### Troubleshooting
If something went wrong or the build failed, the log file will be launched (you can find the log files in `C:\php-sdk`). Otherwise, the log file will not be shown, and they remain in `C:\php-sdk` until you remove them manually.
In addition to the log files in `C:\php-sdk`, you can examine the contents of `C:\php-sdk\phpsdk-build-task.bat`, which is overwritten every time you run the build scripts.
#### Testing mode and/or setting alternative destination
If your main goal is to build the drivers for testing, and/or there is no need to keep the `php-sdk` directory around, you can invoke `py builddrivers.py` with the necessary command-line arguments plus `--TESTING`, which turns on the *testing* mode (it is False by default).
Setting the testing mode automatically turns off the looping mechanism. When the build is finished, you will find a copy of the drivers (unless the build failed) and the `php-sdk` folder in the same directory of these Python scripts.
In addition, you can set an alternative destination using `--DESTPATH=<some valid path>`, which is **None** by default. Note that these two options are *not* available in the interactive mode. However, they are particularly useful for testing purposes (such as testing in a virtual machine) in which these build scripts are copied to a temporary folder. After the drivers have been successfully compiled and copied to the designated location, the temporary folder can be safely removed.

View file

@ -1,311 +0,0 @@
#!/usr/bin/python3
#########################################################################################
#
# Description: This script helps to build drivers in a Windows environment for PHP 7+ (32-bit/64-bit)
#
# Requirement:
# python 3.x
# PHP SDK and PHP Source
# Driver source code folder / GitHub repository
# Visual Studio 2015 (PHP 7.0* and 7.1*) and Visual Studio 2017 (PHP 7.2*)
#
# Execution: Run with command line with required options.
# Examples:
# py builddrivers.py (for interactive mode)
# py builddrivers.py --PHPVER=7.0.22 --ARCH=x64 --THREAD=nts --DRIVER=all --DEBUG
#
# Output: Build the drivers using PHP SDK. When running for local development, if build is unsuccessful,
# the log file will be launched for examination. Otherwise, the drivers will be renamed
# and copied to the designated location (if defined).
#
#############################################################################################
import sys
import shutil
import os.path
import argparse
import subprocess
from buildtools import BuildUtil
class BuildDriver(object):
"""Build sqlsrv and/or pdo_sqlsrv drivers with PHP source with the following properties:
Attributes:
util # BuildUtil object whose constructor takes phpver, driver, arch, thread, debug
repo # GitHub repository
branch # GitHub repository branch
dest_path # alternative destination for the drivers (None for development builds)
rebuild # a boolean flag - whether the user is rebuilding
make_clean # a boolean flag - whether make clean is necessary
source_path # path to a local source folder
testing # whether the user has turned on testing mode
"""
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
self.dest_path = path
self.testing = testing
self.rebuild = False
self.make_clean = False
def show_config(self):
print()
print('PHP Version: ', self.util.phpver)
print('Arch: ', self.util.arch)
print('Thread: ', self.util.thread)
print('Driver: ', self.util.driver)
print('Source: ', self.source_path)
print('Debug enabled: ', self.util.debug_enabled)
print()
def clean_or_remove(self, root_dir, work_dir):
"""Only check this for local development and not rebuilding. If the php source directory
already exists, this will prompt user whether to rebuild, clean, or superclean, the last option
will remove the entire php source directory.
:param root_dir: the C:\ drive
:param work_dir: the directory of this script
:outcome: the old binaries, if exist, will be removed
"""
phpsrc = self.util.phpsrc_root(root_dir)
if os.path.exists( phpsrc ):
print(phpsrc + " exists.")
build_choice = validate_input("(r)ebuild for the same configuration, (c)lean otherwise, (s)uperclean if unsure ", "r/c/s")
self.make_clean = False
if build_choice == 'r':
print('Will rebuild the binaries')
# only the old binaries based on the current configuration will be removed
self.util.remove_prev_build(root_dir)
elif build_choice == 'c':
print('Will make clean')
self.make_clean = True
# all old builds are removed, and this step is necessary because
# the user might have changed the configuration
self.util.remove_old_builds(root_dir)
else:
print('Will remove ' + phpsrc)
os.system('RMDIR /s /q ' + phpsrc)
os.chdir(work_dir) # change back to the working directory
def get_local_source(self, source_path):
"""This assumes interactive mode (not testing) and takes care of getting
the user's input to the path of the local source files for the drivers
"""
while True:
if source_path is None:
source = input('Enter the full path to the source folder: ')
else:
source = input("Hit ENTER to use '" + source_path + "' or provide another path to the source folder: ")
if len(source) == 0:
source = source_path
valid = True
if os.path.exists(source) and os.path.exists(os.path.join(source, 'shared')):
# Checking the existence of 'shared' folder only, assuming
# sqlsrv and/or pdo_sqlsrv are also present if it exists
self.source_path = source
break
print("The path provided is invalid. Please re-enter.")
return source
def build_extensions(self, root_dir, logfile):
"""This takes care of getting the drivers' source files, building the drivers.
If dest_path is defined, the binaries will be copied to the designated destinations.
:param root_dir: the root directory
:param logfile: the name of the logfile
:outcome: the drivers and symbols will renamed and placed in the appropriate location(s)
"""
work_dir = os.path.dirname(os.path.realpath(__file__))
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
# Do not prompt user for input if it's in a testing mode
if not self.testing:
source = self.get_local_source(self.source_path)
print('Copying source files from', source)
os.system('ROBOCOPY ' + source + '\shared ' + work_dir + '\Source\shared /xx /xo ')
os.system('ROBOCOPY ' + source + '\sqlsrv ' + work_dir + '\Source\sqlsrv /xx /xo ')
os.system('ROBOCOPY ' + source + '\pdo_sqlsrv ' + work_dir + '\Source\pdo_sqlsrv /xx /xo ')
print('Start building PHP with the extension...')
# If not testing, dest should be the root drive. Otherwise, dest should be None.
dest = None if self.testing else root_dir
# ext_dir is the directory where we can find the built extension(s)
ext_dir = self.util.build_drivers(self.make_clean, dest, logfile)
# Copy the binaries if a destination path is defined
if self.dest_path is not None:
dest_drivers = os.path.join(self.dest_path, self.util.major_version(), self.util.arch)
dest_symbols = os.path.join(dest_drivers, 'Symbols', self.util.thread)
# All intermediate directories will be created in order to create the leaf directory
if os.path.exists(dest_symbols) == False:
os.makedirs(dest_symbols)
# Now copy all the binaries
if self.util.driver == 'all':
self.util.copy_binary(ext_dir, dest_drivers, 'sqlsrv', '.dll')
self.util.copy_binary(ext_dir, dest_symbols, 'sqlsrv', '.pdb')
self.util.copy_binary(ext_dir, dest_drivers, 'pdo_sqlsrv', '.dll')
self.util.copy_binary(ext_dir, dest_symbols, 'pdo_sqlsrv', '.pdb')
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.
For development, this will loop till the user decides to quit.
"""
self.show_config()
work_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = 'C:' + os.sep
quit = False
while not quit:
if self.testing:
self.make_clean = True
self.util.remove_old_builds(work_dir)
elif not self.rebuild:
self.clean_or_remove(root_dir, work_dir)
logfile = self.util.get_logfile_name()
try:
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)
exit(1)
if not self.testing:
choice = input("Rebuild using the same configuration(yes) or quit (no) [yes/no]: ")
choice = choice.lower()
if choice == 'yes' or choice == 'y' or choice == '':
print('Rebuilding drivers...')
self.make_clean = False
self.rebuild = True
self.util.remove_prev_build(root_dir)
else:
quit = True
else:
quit = True
os.chdir(work_dir)
def validate_input(question, values):
"""Return the user selected value, and it must be valid based on *values*."""
while True:
options = values.split('/')
prompt = '[' + values + ']'
value = input(question + prompt + ': ')
value = value.lower()
if not value in options:
print("An invalid choice is entered. Choose from", prompt)
else:
break
return value
################################### Main Function ###################################
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--PHPVER', help="PHP version, e.g. 7.4.* etc.")
parser.add_argument('--ARCH', choices=['x64', 'x86'])
parser.add_argument('--THREAD', choices=['nts', 'ts'])
parser.add_argument('--DRIVER', default='all', choices=['all', 'sqlsrv', 'pdo_sqlsrv'], help="driver to build (default: all)")
parser.add_argument('--DEBUG', action='store_true', help="enable debug mode (default: False)")
parser.add_argument('--REPO', default='Microsoft', help="GitHub repository (default: Microsoft)")
parser.add_argument('--BRANCH', default='dev', help="GitHub repository branch or tag (default: dev)")
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()
phpver = args.PHPVER
arch = args.ARCH
thread = args.THREAD
driver = args.DRIVER
debug = args.DEBUG
repo = args.REPO
branch = args.BRANCH
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
# will not prompt for drivers' destination path, which is None by default
while True:
# perform some minimal checks
phpver = input("PHP Version (e.g. 7.1.* or 7.2.*): ")
if phpver == '':
print('Empty PHP version entered! Please try again.')
elif phpver[0] < '7':
print('Only PHP 7.0 or above is supported. Please try again.')
else:
break
arch_version = input("64-bit? [y/n]: ")
thread = validate_input("Thread safe? ", "nts/ts")
driver = validate_input("Driver to build? ", "all/sqlsrv/pdo_sqlsrv")
debug_mode = input("Debug enabled? [y/n]: ")
answer = input("Download source from a GitHub repo? [y/n]: ")
if answer == 'yes' or answer == 'y' or answer == '':
repo = input("Name of the repo (hit enter for 'Microsoft'): ")
branch = input("Name of the branch or tag (hit enter for 'dev'): ")
if repo == '':
repo = 'Microsoft'
if branch == '':
branch = 'dev'
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()
debug = debug_mode == 'yes' or debug_mode == 'y' or debug_mode == ''
builder = BuildDriver(phpver,
driver,
arch,
thread,
debug,
repo,
branch,
source,
path,
testing,
no_rename)
builder.build()

View file

@ -1,525 +0,0 @@
#!/usr/bin/python3
#########################################################################################
#
# Description: The class BuildUtil will build Microsoft SQL Server PHP 7+ Drivers
# for 32 bit and 64 bit.
#
# Requirement:
# python 3.x
# PHP SDK and PHP Source
# Driver source code folder
# Git for Windows
# Visual Studio 2015 (PHP 7.0* and 7.1*) and Visual Studio 2017 (PHP 7.2*)
#
# Output: The drivers will be renamed and copied to the specified location.
#
#############################################################################################
import shutil
import os.path
import stat
import datetime
import urllib.request
import zipfile
import fileinput
class BuildUtil(object):
"""Build sqlsrv and/or pdo_sqlsrv drivers with PHP source with the following properties:
Attributes:
phpver # PHP version, e.g. 7.1.*, 7.2.* etc.
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, 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
self.vc = ''
def major_version(self):
"""Return the major version number based on the PHP version."""
return self.phpver[0:3]
def version_label(self):
"""Return the version label based on the PHP version."""
major_ver = self.major_version()
version = major_ver[0] + major_ver[2]
return version
def driver_name(self, driver, suffix):
"""Return the *driver* name with *suffix* after PHP is successfully compiled."""
return 'php_' + driver + suffix
def driver_new_name(self, driver, suffix):
"""Return the *driver* name with *suffix* based on PHP version and thread."""
version = self.version_label()
return 'php_' + driver + '_' + version + '_' + self.thread + suffix
def determine_compiler(self, sdk_dir, vs_ver):
"""Return the compiler version using vswhere.exe."""
vswhere = os.path.join(sdk_dir, 'php-sdk', 'bin', 'vswhere.exe')
if not os.path.exists(vswhere):
print('Could not find ' + vswhere)
exit(1)
# If both VS 2017 and VS 2019 are installed, if we check only version 15,
# both versions are returned.
# For example, temp.txt would have the following values (in this order):
# 16.1.29009.5
# 15.9.28307.344
# But if only VS 2017 is present, temp.txt will only have one value like this:
# 15.9.28307.344
# Likewise, if only VS 2019 is present, temp.txt contains only the one for 16.
# We can achieve the above by checking for version [15,16), in which case
# even if both compilers are present, it only returns one. If only VS 2019
# exists, temp.txt is empty
command = '{0} -version [{1},{2}) -property installationVersion '.format(vswhere, vs_ver, vs_ver + 1)
os.system(command + ' > temp.txt')
# Read the first line from temp.txt
with open('temp.txt', 'r') as f:
ver = f.readline()
print('Version: ' + ver)
vc = ver[:2]
if vc == '15':
return 'vc15'
else: # For VS2019, it's 'vs' instead of 'vc'
return 'vs16'
def compiler_version(self, sdk_dir):
"""Return the appropriate compiler version based on PHP version."""
if self.vc == '':
VC = 'vc15'
version = self.version_label()
if version[0] == '8': # Compiler version for PHP 8.0 or above
VC = 'vs16'
self.vc = VC
print('Compiler: ' + self.vc)
return self.vc
def phpsrc_root(self, sdk_dir):
"""Return the path to the PHP source folder based on *sdk_dir*."""
vc = self.compiler_version(sdk_dir)
return os.path.join(sdk_dir, 'php-sdk', 'phpdev', vc, self.arch, 'php-'+self.phpver+'-src')
def build_abs_path(self, sdk_dir):
"""Return the absolute path to the PHP build folder based on *sdk_dir*."""
phpsrc = self.phpsrc_root(sdk_dir)
build_dir = 'Release'
if self.debug_enabled:
build_dir = 'Debug'
if self.thread == 'ts':
build_dir = build_dir + '_TS'
if self.arch == 'x64':
build_dir = self.arch + os.sep + build_dir
return os.path.join(phpsrc, build_dir)
def remove_old_builds(self, sdk_dir):
"""Remove the extensions, e.g. the driver subfolders in php-7.*-src\ext."""
if not os.path.exists(os.path.join(sdk_dir, 'php-sdk')):
print('No old builds to be removed...')
return
print('Removing old builds...')
phpsrc = self.phpsrc_root(sdk_dir)
ext_path = os.path.join(phpsrc, 'ext')
if os.path.exists( ext_path ):
shutil.rmtree(os.path.join(ext_path, 'sqlsrv'), ignore_errors=True)
shutil.rmtree(os.path.join(ext_path, 'pdo_sqlsrv'), ignore_errors=True)
if self.arch == 'x64':
shutil.rmtree(os.path.join(phpsrc, self.arch), ignore_errors=True)
else:
shutil.rmtree(os.path.join(phpsrc, 'Debug'), ignore_errors=True)
shutil.rmtree(os.path.join(phpsrc, 'Debug_TS'), ignore_errors=True)
shutil.rmtree(os.path.join(phpsrc, 'Release'), ignore_errors=True)
shutil.rmtree(os.path.join(phpsrc, 'Release_TS'), ignore_errors=True)
def remove_prev_build(self, sdk_dir):
"""Remove all binaries and source code in the Release* or Debug*
folders according to the current configuration
"""
if not os.path.exists(os.path.join(sdk_dir, 'php-sdk')):
print('No old builds to be removed...')
return
print('Removing previous build...')
build_dir = self.build_abs_path(sdk_dir)
if not os.path.exists(build_dir):
return
os.chdir(build_dir)
os.system('DEL *sqlsrv*')
# remove the extensions in the phpsrc's release* or debug* folder's ext subfolder
release_ext_path = os.path.join(build_dir, 'ext')
if os.path.exists( release_ext_path ):
shutil.rmtree(os.path.join(release_ext_path, 'sqlsrv'), ignore_errors=True)
shutil.rmtree(os.path.join(release_ext_path, 'pdo_sqlsrv'), ignore_errors=True)
# next remove the binaries too
os.chdir(release_ext_path)
os.system('DEL *sqlsrv*')
@staticmethod
def get_logfile_name():
"""Return the filename for the log file based on timestamp."""
return 'Build_' + datetime.datetime.now().strftime("%Y%m%d_%H%M") + '.log'
@staticmethod
def update_file_content(file, search_str, new_str):
"""Find *search_str* and replace it by *new_str* in a *file*"""
os.chmod(file, stat.S_IWRITE)
with fileinput.FileInput(file, inplace=True) as f:
for line in f:
print(line.replace(search_str, new_str), end='')
@staticmethod
def generateMMDD():
"""Return the generated Microsoft PHP Build Version Number"""
d = datetime.date.today()
startYear = 2009
startMonth = 4
passYear = int( '%02d' % d.year ) - startYear
passMonth = int( '%02d' % d.month ) - startMonth
MM = passYear * 12 + passMonth
dd = d.day
MMDD = "" + str( MM )
if( dd < 10 ):
return MMDD + "0" + str( dd )
else:
return MMDD + str( dd )
@staticmethod
def get_driver_version(version_file):
"""Read the *version_file* and return the driver version."""
with open(version_file) as f:
for line in f:
if 'SQLVERSION_MAJOR' in line: # major version
major = line.split()[2]
elif 'SQLVERSION_MINOR' in line: # minor version
minor = line.split()[2]
elif 'SQLVERSION_PATCH' in line: # patch
patch = line.split()[2]
break
return major + '.' + minor + '.' + patch
@staticmethod
def write_lines_to_copy_source(driver, file):
"""Write to file the commands to copy *driver* source."""
source = '%currDir%' + os.sep + 'Source' + os.sep + driver
dest = '%phpSrc%' + os.sep + 'ext' + os.sep + driver
file.write('@CALL ROBOCOPY ' + source + ' ' + dest + ' /s /xx /xo' + os.linesep)
source = '%currDir%' + os.sep + 'Source' + os.sep + 'shared'
dest = '%phpSrc%' + os.sep + 'ext' + os.sep + driver + os.sep + 'shared'
file.write('@CALL ROBOCOPY ' + source + ' ' + dest + ' /s /xx /xo' + os.linesep)
@staticmethod
def download_msphpsql_source(repo, branch, dest_folder = 'Source'):
"""Download to *dest_folder* the msphpsql archive of the specified
GitHub *repo* and *branch*. The downloaded files will be removed by default.
"""
try:
work_dir = os.path.dirname(os.path.realpath(__file__))
temppath = os.path.join(work_dir, 'temp')
# There is no need to remove tree -
# for Bamboo, it will be cleaned up eventually
# for local development, this can act as a cached copy of the repo
if not os.path.exists(temppath):
os.makedirs(temppath)
os.chdir(temppath)
msphpsqlFolder = os.path.join(temppath, 'msphpsql-' + branch)
url = 'https://github.com/' + repo + '/msphpsql.git'
command = 'git clone ' + url + ' -b ' + branch + ' --single-branch --depth 1 ' + msphpsqlFolder
os.system(command)
source = os.path.join(msphpsqlFolder, 'source')
os.chdir(work_dir)
os.system('ROBOCOPY ' + source + '\shared ' + dest_folder + '\shared /xx /xo')
os.system('ROBOCOPY ' + source + '\pdo_sqlsrv ' + dest_folder + '\pdo_sqlsrv /xx /xo')
os.system('ROBOCOPY ' + source + '\sqlsrv ' + dest_folder + '\sqlsrv /xx /xo')
except:
print('Error occurred when downloading source')
raise
def update_driver_source(self, source_dir, driver):
"""Update the *driver* source in *source_path* with the
latest version, file descriptions, etc.
If debug is enabled, will remove the optimization flag
"""
driver_dir = os.path.join(source_dir, driver)
if self.debug_enabled:
# Adding linker flags for creating more debugging information in the binaries
print('Adding linker flags for', driver)
config_file = os.path.join(driver_dir, 'config.w32')
if driver == 'sqlsrv':
self.update_file_content(config_file, 'ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf" );', 'ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf /debugtype:cv,fixup" );')
elif driver == 'pdo_sqlsrv':
self.update_file_content(config_file, 'ADD_FLAG( "LDFLAGS_PDO_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf" );', 'ADD_FLAG( "LDFLAGS_PDO_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf /debugtype:cv,fixup" );')
# Update Template.rc
template_file = os.path.join(driver_dir, 'template.rc')
if driver == 'sqlsrv':
drivername = self.driver_new_name(driver, '.dll')
self.update_file_content(template_file, 'FILE_NAME \"\\0\"', '"' + drivername + '\\0"')
self.update_file_content(template_file, '\"Microsoft Drivers for PHP for SQL Server\\0\"', '"Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)\\0"')
elif driver == 'pdo_sqlsrv':
drivername = self.driver_new_name(driver, '.dll')
self.update_file_content(template_file, 'FILE_NAME \"\\0\"', '"' + drivername + '\\0"')
self.update_file_content(template_file, '\"Microsoft Drivers for PHP for SQL Server\\0\"', '"Microsoft Drivers for PHP for SQL Server (PDO Driver)\\0"')
# Update Version.h
version_file = os.path.join(source_dir, 'shared', 'version.h')
build_number = self.generateMMDD()
self.update_file_content(version_file, 'SQLVERSION_BUILD 0', 'SQLVERSION_BUILD ' + build_number)
# get the latest version
version = self.get_driver_version(version_file) + '.' + build_number
print('Driver version is: ', version)
# Update CREDIT file
credits_file = os.path.join(driver_dir, 'CREDITS')
if driver == 'sqlsrv':
self.update_file_content(credits_file, 'Microsoft Drivers for PHP for SQL Server', 'Microsoft Drivers ' + version + ' for PHP for SQL Server (' + self.driver.upper() + ' driver)')
elif driver == 'pdo_sqlsrv':
self.update_file_content(credits_file, 'Microsoft Drivers for PHP for SQL Server (PDO driver)', 'Microsoft Drivers ' + version + ' for PHP for SQL Server (' + self.driver.upper() + ' driver)')
def generate_build_options(self):
"""Return the generated build configuration and arguments"""
cmd_line = ''
if self.debug_enabled:
cmd_line = ' --enable-debug '
if self.driver == 'all':
cmd_line = ' --enable-sqlsrv=shared --enable-pdo --with-pdo-sqlsrv=shared ' + cmd_line
else:
if self.driver == 'sqlsrv':
cmd_line = ' --enable-sqlsrv=shared ' + cmd_line
else: # pdo_sqlsrv
cmd_line = ' --enable-pdo --with-pdo-sqlsrv=shared ' + cmd_line
cmd_line = 'cscript configure.js --disable-all --enable-cli --enable-cgi --enable-json --enable-embed' + cmd_line
if self.thread == 'nts':
cmd_line = cmd_line + ' --disable-zts'
return cmd_line
def create_local_batch_file(self, make_clean, cmd_line, log_file):
"""Generate the batch file to be picked up by the PHP starter script."""
filename = 'phpsdk-build-task.bat'
print('Generating ', filename)
try:
file = open(filename, 'w')
file.write('@ECHO OFF' + os.linesep)
file.write('SET currDir=%CD%' + os.linesep)
file.write('SET LOG_NAME=%currDir%\\' + log_file + os.linesep)
file.write('@CALL phpsdk_buildtree phpdev > %LOG_NAME% 2>&1' + os.linesep)
# for PHP version with release tags, such as 'RC', 'beta', etc.
# we need to remove the hyphen '-' between the version number and tag
# because in https://github.com/php/php-src the released tags have no hyphens
php_tag = 'php-' + self.phpver.replace('-', '')
php_src = 'php-' + self.phpver +'-src'
# if not exists, check out the specified tag
file.write('IF NOT EXIST ' + php_src + ' @CALL git clone -b ' + php_tag + ' --depth 1 --single-branch https://github.com/php/php-src.git ' + php_src + os.linesep)
file.write('CD ' + php_src + os.linesep)
file.write('SET phpSrc=%CD%' + os.linesep)
file.write('@CALL phpsdk_deps -u >> %LOG_NAME% 2>&1' + os.linesep)
# copy source files to extension
if self.driver == 'all':
self.write_lines_to_copy_source('sqlsrv', file)
self.write_lines_to_copy_source('pdo_sqlsrv', file)
else:
self.write_lines_to_copy_source(self.driver, file)
# configure and build
file.write('@CALL buildconf --force >> %LOG_NAME% 2>&1' + os.linesep)
file.write('@CALL ' + cmd_line + ' >> %LOG_NAME% 2>&1' + os.linesep)
if make_clean:
file.write('nmake clean >> %LOG_NAME% 2>&1' + os.linesep)
file.write('nmake >> %LOG_NAME% 2>&1' + os.linesep)
file.write('exit' + os.linesep)
file.close()
return filename
except:
print('Cannot create ', filename)
def build_drivers(self, make_clean = False, dest = None, log_file = None):
"""Build sqlsrv/pdo_sqlsrv extensions for PHP, assuming the Source folder
exists in the working directory, and this folder will be removed when the build
is complete.
"""
work_dir = os.path.dirname(os.path.realpath(__file__))
# First, update the driver source file contents
source_dir = os.path.join(work_dir, 'Source')
if self.driver == 'all':
self.update_driver_source(source_dir, 'sqlsrv')
self.update_driver_source(source_dir, 'pdo_sqlsrv')
else:
self.update_driver_source(source_dir, self.driver)
# Next, generate the build configuration and arguments
cmd_line = self.generate_build_options()
print('cmd_line: ' + cmd_line)
# Generate a batch file based on the inputs
if log_file is None:
log_file = self.get_logfile_name()
batch_file = self.create_local_batch_file(make_clean, cmd_line, log_file)
# Reference: https://github.com/OSTC/php-sdk-binary-tools
# Clone the master branch of PHP sdk if the directory does not exist
print('Downloading the latest php SDK...')
# if *dest* is None, simply use the current working directory
sdk_dir = dest
copy_to_ext = True # this determines where to copy the binaries to
if dest is None:
sdk_dir = work_dir
copy_to_ext = False
phpSDK = os.path.join(sdk_dir, 'php-sdk')
if not os.path.exists( phpSDK ):
os.system('git clone https://github.com/OSTC/php-sdk-binary-tools.git --branch master --single-branch --depth 1 ' + phpSDK)
os.chdir(phpSDK)
os.system('git pull ')
print('Done cloning the latest php SDK...')
# Move the generated batch file to phpSDK for the php starter script
print('Moving the sdk bath file over...')
sdk_batch_file = os.path.join(phpSDK, batch_file)
if os.path.exists(sdk_batch_file):
os.remove(sdk_batch_file)
shutil.move(os.path.join(work_dir, batch_file), phpSDK)
print('Checking if source exists...')
sdk_source = os.path.join(phpSDK, 'Source')
# Sometimes, for various reasons, the Source folder from previous build
# might exist in phpSDK. If so, remove it first
if os.path.exists(sdk_source):
os.chmod(sdk_source, stat.S_IWRITE)
shutil.rmtree(sdk_source, ignore_errors=True)
shutil.move(source_dir, phpSDK)
# Invoke phpsdk-<vc>-<arch>.bat
vc = self.compiler_version(sdk_dir)
starter_script = 'phpsdk-' + vc + '-' + self.arch + '.bat'
print('Running starter script: ', starter_script)
os.system(starter_script + ' -t ' + batch_file)
# Now we can safely remove the Source folder, because its contents have
# 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, 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)
return ext_dir
def rename_binary(self, path, driver):
"""Rename the *driver* binary (sqlsrv or pdo_sqlsrv) (only the dlls)."""
driver_old_name = self.driver_name(driver, '.dll')
driver_new_name = self.driver_new_name(driver, '.dll')
os.rename(os.path.join(path, driver_old_name), os.path.join(path, driver_new_name))
def rename_binaries(self, sdk_dir):
"""Rename the sqlsrv and/or pdo_sqlsrv dlls according to the PHP
version and thread.
"""
# Derive the path to where the extensions are located
ext_dir = self.build_abs_path(sdk_dir)
print("Renaming binaries in ", ext_dir)
if self.driver == 'all':
self.rename_binary(ext_dir, 'sqlsrv')
self.rename_binary(ext_dir, 'pdo_sqlsrv')
else:
self.rename_binary(ext_dir, self.driver)
def copy_binary(self, from_dir, dest_dir, driver, suffix):
"""Copy sqlsrv or pdo_sqlsrv binary (based on *suffix*) to *dest_dir*."""
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
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':
self.copy_binary(build_dir, dest_dir, 'sqlsrv', '.dll')
self.copy_binary(build_dir, dest_dir, 'sqlsrv', '.pdb')
self.copy_binary(build_dir, dest_dir, 'pdo_sqlsrv', '.dll')
self.copy_binary(build_dir, dest_dir, 'pdo_sqlsrv', '.pdb')
else:
self.copy_binary(build_dir, dest_dir, self.driver, '.dll')
self.copy_binary(build_dir, dest_dir, self.driver, '.pdb')
return dest_dir

View file

@ -1,143 +0,0 @@
#!/usr/bin/python3
#########################################################################################
#
# Description: This contains helper methods for source indexing
#
# Requirement:
# python 3.x
# srctool.exe and pdbstr.exe
#
#############################################################################################
import os.path
import argparse
import subprocess
from subprocess import Popen, PIPE
def write_index(index_filename, tag_version):
"""This writes to a temporary index file for the pdbstr tool
For example
SRCSRV: ini ------------------------------------------------
VERSION=1
SRCSRV: variables ------------------------------------------
PATH=%var2%
SRCSRVTRG=%TARG%\%PDBVERSION%\%fnbksl%(%var2%)
SRCURL=https://raw.githubusercontent.com/Microsoft/msphpsql/%SRCVERSION%/source/%PATH%
SRCSRVCMD=powershell -Command "Invoke-WebRequest -Uri '%SRCURL%' -OutFile '%SRCSRVTRG%'"
SRCVERSION=v5.6.0
PDBVERSION=v5.6.0
For example
"""
with open(index_filename, 'w') as f:
f.write('SRCSRV: ini ------------------------------------------------' + os.linesep)
f.write('VERSION=1' + os.linesep)
f.write('SRCSRV: variables ------------------------------------------' + os.linesep)
f.write('PATH=%var2%' + os.linesep)
f.write('SRCSRVTRG=%TARG%\%PDBVERSION%\%fnbksl%(%var2%)' + os.linesep)
f.write('SRCURL=https://raw.githubusercontent.com/Microsoft/msphpsql/%SRCVERSION%/source/%PATH%' + os.linesep)
f.write('SRCSRVCMD=powershell -Command ')
f.write('\"Invoke-WebRequest -Uri \'%SRCURL%\' -OutFile \'%SRCSRVTRG%\'\"' + os.linesep)
f.write('SRCVERSION=' + tag_version + os.linesep)
f.write('PDBVERSION=' + tag_version + os.linesep)
def append_source_filess(index_filename, source_files, driver):
"""This appends the paths to different source files to the temporary index file
For example
SRCSRV: source files ---------------------------------------
c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\pdo_dbh.cpp*pdo_sqlsrv/pdo_dbh.cpp
c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\pdo_init.cpp*pdo_sqlsrv/pdo_init.cpp
... ...
c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\shared\core_stream.cpp*shared/core_stream.cpp
c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\shared\core_util.cpp*shared/core_util.cpp
SRCSRV: end ------------------------------------------------
"""
failed = False
with open(index_filename, 'a') as idx_file:
idx_file.write('SRCSRV: source files ---------------------------------------' + os.linesep)
with open(source_files, 'r') as src_file:
for line in src_file:
pos = line.find('shared')
if (pos > 0): # it's a nested folder, so it must be positive
relative_path = line[pos:]
src_line = line[:-1] + '*' + relative_path.replace('\\', '/')
else: # not a file in the shared folder
pos = line.find(driver)
if (pos <= 0):
print('ERROR: Expected to find', driver, 'in', line)
failed = True
break
else:
relative_path = line[pos:]
src_line = line[:-1] + '*' + relative_path.replace('\\', '/')
idx_file.write(src_line)
idx_file.write('SRCSRV: end ------------------------------------------------' + os.linesep)
return failed
def run_indexing_tools(pdbfile, driver, tag_version):
"""This invokes the source indexing tools, srctool.exe and pdbstr.exe
:param pdbfile: the absolute path to the symbol file
:param driver: either sqlsrv or pdo_sqlsrv
:param tag_version: tag version for source indexing
:outcome: the driver pdb file will be source indexed
"""
# run srctool.exe to get all driver's source files from the PDB file
# srctool.exe -r <PDBfile> | find "<driver>\" | sort > files.txt
batch_filename = 'runsrctool.bat'
index_filename = 'idx.txt'
source_files = 'files.txt'
with open(batch_filename, 'w') as batch_file:
batch_file.write('@ECHO OFF' + os.linesep)
batch_file.write('@CALL srctool -r %1 | find "%2\\" | sort > ' + source_files + ' 2>&1' + os.linesep)
get_source_filess = batch_filename + ' {0} {1} '
get_source_filess_cmd = get_source_filess.format(pdbfile, driver)
subprocess.call(get_source_filess_cmd)
# create an index file using the above inputs for pdbstr.exe
write_index(index_filename, tag_version)
failed = append_source_filess(index_filename, source_files, driver)
if failed:
print("ERROR: Failed to prepare the temporary index file for the pdbstr tool")
exit(1)
# run pdbstr.exe to insert the information into the PDB file
# pdbstr.exe -w -p:<PDBfile> -i:idx.txt -s:srcsrv
pdbstr_str = 'pdbstr.exe -w -p:{0} -i:{1} -s:srcsrv'
pdbstr_cmd = pdbstr_str.format(pdbfile, index_filename)
subprocess.call(pdbstr_cmd)
os.remove(batch_filename)
os.remove(index_filename)
os.remove(source_files)
################################### Main Function ###################################
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('PDBFILE', help="the path to the pdb file for source indexing")
parser.add_argument('DRIVER', choices=['sqlsrv', 'pdo_sqlsrv'], help="driver name of this pdb file")
parser.add_argument('TAG_VERSION', help="the tag version for source indexing (e.g. v5.6.0)")
parser.add_argument('TOOLS_PATH',help="the path to the source indexing tools")
args = parser.parse_args()
srctool_exe = os.path.join(args.TOOLS_PATH, 'srctool.exe')
pdbstr_exe = os.path.join(args.TOOLS_PATH, 'pdbstr.exe')
if not os.path.exists(srctool_exe) or not os.path.exists(pdbstr_exe):
print('ERROR: Missing the required source indexing tools')
exit(1)
work_dir = os.path.dirname(os.path.realpath(__file__))
os.chdir(args.TOOLS_PATH)
print('Source indexing begins...')
run_indexing_tools(args.PDBFILE, args.DRIVER.lower(), args.TAG_VERSION)
print('Source indexing done')
os.chdir(work_dir)

View file

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

View file

@ -1,16 +0,0 @@
set -e
testConnection="/opt/mssql-tools/bin/sqlcmd -S sql -U sa -P Password123"
for run in {1..10}; do
>&2 echo "SQL Server is starting up.."
if $testConnection; then
>&2 echo "SQL Server is up!"
break;
else
sleep 6
fi
done

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

121
package.xml Normal file
View file

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
<name>sqlsrv</name>
<channel>pecl.php.net</channel>
<summary>Microsoft Drivers for PHP for SQL Server (SQLSRV)</summary>
<description>The Microsoft Drivers for PHP for 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 PDO for accessing data in all editions of SQL Server 2012 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 package contains only the SQLSRV driver.*
</description>
<lead>
<name>Meet Bhagdev</name>
<user>meetbhagdev</user>
<email>meetb@microsoft.com</email>
<active>no</active>
</lead>
<lead>
<name>Jay Kint</name>
<user>jkint</user>
<email>jaykint@microsoft.com</email>
<active>no</active>
</lead>
<lead>
<name>Marie Barwin</name>
<user>mbar</user>
<email>v-mabarw@microsoft.com</email>
<active>yes</active>
</lead>
<lead>
<name>Jenny Tam</name>
<user>yitam</user>
<email>v-yitam@microsoft.com</email>
<active>no</active>
</lead>
<date>2022-05-13</date>
<time>21:44:45</time>
<version>
<release>5.10.1</release>
<api>5.10.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="https://opensource.org/licenses/mit">The MIT License (MIT)</license>
<notes>
[Added]
- Pull request [#1382](https://github.com/microsoft/msphpsql/pull/1382) - Support for ActiveDirectoryIntegrated authentication
[Fixed]
- Pull request [#1374](https://github.com/microsoft/msphpsql/pull/1374) - Fixed ActiveDirectoryMsi Authentication behavior when specified UID by laclefyoshi
[Limitations]
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
[Known Issues]
- This preview release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) &lt; 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC &lt;= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
</notes>
<contents>
<dir name="/">
<file md5sum="da4d3c1d4392f2e63b36250266cdf9b4" name="CREDITS" role="doc" />
<file md5sum="5367e8bd4b5c4f8034578525fbaff449" name="LICENSE" role="doc" />
<file md5sum="154f90f0c73a9fc2e1a7c06548a87a8a" name="config.m4" role="src" />
<file md5sum="ee5f439b8fcce700cbbe2fdeaf3c168b" name="config.w32" role="src" />
<file md5sum="225e7b2143a9c9cf89fe5b2b1021763e" name="conn.cpp" role="src" />
<file md5sum="271969b6a7a84917d01c0a65e7de056e" name="init.cpp" role="src" />
<file md5sum="4b70081ad33ad0df344072c9ac9c3a50" name="php_sqlsrv.h" role="src" />
<file md5sum="19cb3098e652003fbf923a7913920763" name="php_sqlsrv_int.h" role="src" />
<file md5sum="e88a54492774fd7454c958bad5be9d26" name="shared/FormattedPrint.cpp" role="src" />
<file md5sum="958afdbccf2140006c5d0f31cf4a1f76" name="shared/FormattedPrint.h" role="src" />
<file md5sum="6d437757bcd8cf8ba3ed42f54a12854b" name="shared/StringFunctions.cpp" role="src" />
<file md5sum="96329050a701facc6653564a1d497316" name="shared/StringFunctions.h" role="src" />
<file md5sum="43b0ab59acdb12feed86124f5e0fb07e" name="shared/core_conn.cpp" role="src" />
<file md5sum="8a4283011a38594a4564293f6bca9484" name="shared/core_init.cpp" role="src" />
<file md5sum="e456bcea9ac3ad1bb6c7b5f33718bc63" name="shared/core_results.cpp" role="src" />
<file md5sum="6fc6834030518ef5f6f6dfb0847655ae" name="shared/core_sqlsrv.h" role="src" />
<file md5sum="2e47d9ce2f0c1203da38935497412cf5" name="shared/core_stmt.cpp" role="src" />
<file md5sum="bbb7c8c8243838e2448f1b0f71ba34a7" name="shared/core_stream.cpp" role="src" />
<file md5sum="b27d168f625fa3921f8dd4d0d42a3da5" name="shared/core_util.cpp" role="src" />
<file md5sum="4d95c2179b17ceef3ae795dfe1c8fbde" name="shared/globalization.h" role="src" />
<file md5sum="a49783c30f55ee89d50f041cb7101b82" name="shared/interlockedatomic.h" role="src" />
<file md5sum="d8d435fa231f022ac3d4ee07737cc7da" name="shared/interlockedatomic_gcc.h" role="src" />
<file md5sum="077c93cbc32fb2b4d75da0a60d860a00" name="shared/interlockedslist.h" role="src" />
<file md5sum="f93f1b6fe442bbb48f3934df73e2be46" name="shared/localization.hpp" role="src" />
<file md5sum="50243ab88f61fe6e9ca04154af4fc42a" name="shared/localizationimpl.cpp" role="src" />
<file md5sum="3d8a3276eda8880a7edd42baf972eb10" name="shared/msodbcsql.h" role="src" />
<file md5sum="7ebc8a074bceac47649f6e3bcd1759fc" name="shared/sal_def.h" role="src" />
<file md5sum="cf350aec37652ce03c7e44729d422b10" name="shared/typedefs_for_linux.h" role="src" />
<file md5sum="6439f1bed7d9d654809a3c5a6d2a6a5c" name="shared/version.h" role="src" />
<file md5sum="29980e0674d44758c8521ed27cb39ce7" name="shared/xplat.h" role="src" />
<file md5sum="d99a98a25d4f9b2ae1c3bd129b4fe64a" name="shared/xplat_intsafe.h" role="src" />
<file md5sum="9456033e0e237f49d7fed6a851cd7d7b" name="shared/xplat_winerror.h" role="src" />
<file md5sum="8073aaa724a423504b4e65ad5effbd2f" name="shared/xplat_winnls.h" role="src" />
<file md5sum="0890995e5cf829c078f95168a8c82e13" name="stmt.cpp" role="src" />
<file md5sum="8690cfbbaf8c391d42bb3355272d1943" name="template.rc" role="src" />
<file md5sum="e28c64727a2d671515a4537a4035d751" name="util.cpp" role="src" />
</dir>
</contents>
<dependencies>
<required>
<php>
<min>7.3.0</min>
</php>
<pearinstaller>
<min>1.4.0b1</min>
</pearinstaller>
<arch>
<pattern>linux-*-i?86-*</pattern>
<conflicts/>
</arch>
</required>
</dependencies>
<providesextension>sqlsrv</providesextension>
<extsrcrelease />
</package>

View file

@ -1,73 +0,0 @@
<?php
echo "\n";
$serverName = "tcp:yourserver.database.windows.net,1433";
$database = "yourdatabase";
$uid = "yourusername";
$pwd = "yourpassword";
//Establishes the connection
$conn = new PDO("sqlsrv:server=$serverName ; Database = $database", $uid, $pwd);
//Select Query
$tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
//Executes the query
$getProducts = $conn->query($tsql);
//Error handling
FormatErrors($conn->errorInfo());
$productCount = 0;
$ctr = 0;
?>
<h1> First 10 results are : </h1>
<?php
while ($row = $getProducts->fetch(PDO::FETCH_ASSOC)) {
if ($ctr>9) {
break;
}
$ctr++;
echo($row['CompanyName']);
echo("<br/>");
$productCount++;
}
$getProducts = null;
$tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.* VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
//Insert query
$insertReview = $conn->query($tsql);
FormatErrors($conn->errorInfo());
?>
<h1> Product Key inserted is :</h1>
<?php
while ($row = $insertReview->fetch(PDO::FETCH_ASSOC)) {
echo($row['ProductID']."<br/>");
}
$insertReview = null;
//Delete Query
//We are deleting the same record
$tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?";
$param = "SQL New 1";
$deleteReview = $conn->prepare($tsql);
$deleteReview->bindParam(1, $param);
$deleteReview->execute();
FormatErrors($deleteReview->errorInfo());
function FormatErrors($error)
{
/* Display error. */
echo "Error information: <br/>";
echo "SQLSTATE: ".$error[0]."<br/>";
echo "Code: ".$error[1]."<br/>";
echo "Message: ".$error[2]."<br/>";
}
?>

View file

@ -1,71 +0,0 @@
<?php
echo "\n";
$serverName = "tcp:yourserver.database.windows.net,1433";
$connectionOptions = array("Database"=>"yourdatabase", "Uid"=>"yourusername", "PWD"=>"yourpassword");
//Establishes the connection
$conn = sqlsrv_connect($serverName, $connectionOptions);
//Select Query
$tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
//Executes the query
$getProducts = sqlsrv_query($conn, $tsql);
//Error handling
if ($getProducts == false) {
die(FormatErrors(sqlsrv_errors()));
}
$productCount = 0;
$ctr = 0;
?>
<h1> First 10 results are : </h1>
<?php
while ($row = sqlsrv_fetch_array($getProducts, SQLSRV_FETCH_ASSOC)) {
if ($ctr>9) {
break;
}
$ctr++;
echo($row['CompanyName']);
echo("<br/>");
$productCount++;
}
sqlsrv_free_stmt($getProducts);
$tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
//Insert query
$insertReview = sqlsrv_query($conn, $tsql);
if ($insertReview == false) {
die(FormatErrors(sqlsrv_errors()));
}
?>
<h1> Product Key inserted is :</h1>
<?php
while ($row = sqlsrv_fetch_array($insertReview, SQLSRV_FETCH_ASSOC)) {
echo($row['ProductID']);
}
sqlsrv_free_stmt($insertReview);
//Delete Query
//We are deleting the same record
$tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?";
$params = array("SQL New 1");
$deleteReview = sqlsrv_prepare($conn, $tsql, $params);
if ($deleteReview == false) {
die(FormatErrors(sqlsrv_errors()));
}
if (sqlsrv_execute($deleteReview) == false) {
die(FormatErrors(sqlsrv_errors()));
}
function FormatErrors($errors)
{
/* Display errors. */
echo "Error information: <br/>";
foreach ($errors as $error) {
echo "SQLSTATE: ".$error['SQLSTATE']."<br/>";
echo "Code: ".$error['code']."<br/>";
echo "Message: ".$error['message']."<br/>";
}
}
?>

View file

@ -1,11 +0,0 @@
#!/bin/bash
if [[ -z $1 ]]; then
cp -rf "$PWD"/shared "$PWD"/sqlsrv
cp -rf "$PWD"/shared "$PWD"/pdo_sqlsrv
else
[[ -d $1 ]] || { echo "No such path!"; exit 1; }
cp -rf "$PWD"/sqlsrv "$1"
cp -rf "$PWD"/pdo_sqlsrv "$1"
cp -rf "$PWD"/shared "$1"/sqlsrv
cp -rf "$PWD"/shared "$1"/pdo_sqlsrv
fi

View file

@ -1 +0,0 @@
Microsoft Drivers for PHP for SQL Server (PDO driver)

View file

@ -1,101 +0,0 @@
dnl ----------------------------------------------------------------------------------------------------------------------------------
dnl File: config.m4
dnl
dnl Contents: the code that will go into the configure script, indicating options,
dnl external libraries and includes, and what source files are to be compiled.
dnl
dnl Microsoft Drivers 5.10 for PHP for SQL Server
dnl Copyright(c) Microsoft Corporation
dnl All rights reserved.
dnl MIT License
dnl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
dnl to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
dnl 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 :
dnl The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
dnl THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
dnl 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
dnl IN THE SOFTWARE.
dnl ---------------------------------------------------------------------------------------------------------------------------------
PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support,
[ --with-pdo_sqlsrv Include pdo_sqlsrv support])
if test "$PHP_PDO_SQLSRV" != "no"; then
if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then
AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.])
fi
ifdef([PHP_CHECK_PDO_INCLUDES],
[
PHP_CHECK_PDO_INCLUDES
],[
AC_MSG_CHECKING([for PDO includes])
if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then
pdo_cv_inc_path=$abs_srcdir/ext
elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then
pdo_cv_inc_path=$abs_srcdir/ext
elif test -f $phpincludedir/ext/pdo/php_pdo_driver.h; then
pdo_cv_inc_path=$phpincludedir/ext
else
AC_MSG_ERROR([Cannot find php_pdo_driver.h.])
fi
AC_MSG_RESULT($pdo_cv_inc_path)
])
pdo_sqlsrv_src_class="\
pdo_dbh.cpp \
pdo_parser.cpp \
pdo_util.cpp \
pdo_init.cpp \
pdo_stmt.cpp \
"
shared_src_class="\
shared/core_conn.cpp \
shared/core_results.cpp \
shared/core_stream.cpp \
shared/core_init.cpp \
shared/core_stmt.cpp \
shared/core_util.cpp \
shared/FormattedPrint.cpp \
shared/localizationimpl.cpp \
shared/StringFunctions.cpp \
"
AC_MSG_CHECKING([for PDO_SQLSRV headers])
if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h; then
pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/
elif test -f $srcdir/shared/core_sqlsrv.h; then
pdo_sqlsrv_inc_path=$srcdir/shared/
else
AC_MSG_ERROR([Cannot find PDO_SQLSRV headers])
fi
AC_MSG_RESULT($pdo_sqlsrv_inc_path)
CXXFLAGS="$CXXFLAGS -std=c++11"
CXXFLAGS="$CXXFLAGS -D_FORTIFY_SOURCE=2 -O2"
CXXFLAGS="$CXXFLAGS -fstack-protector"
HOST_OS_ARCH=`uname`
if test "${HOST_OS_ARCH}" = "Darwin"; then
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
else
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-z,now"
IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'`
IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='`
if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then
AC_DEFINE(__MUSL__, 1, [ ])
fi
fi
PHP_REQUIRE_CXX()
PHP_ADD_LIBRARY(stdc++, 1, PDO_SQLSRV_SHARED_LIBADD)
PHP_ADD_LIBRARY(odbc, 1, PDO_SQLSRV_SHARED_LIBADD)
PHP_ADD_LIBRARY(odbcinst, 1, PDO_SQLSRV_SHARED_LIBADD)
AC_DEFINE(HAVE_PDO_SQLSRV, 1, [ ])
PHP_ADD_INCLUDE([$pdo_sqlsrv_inc_path])
PHP_NEW_EXTENSION(pdo_sqlsrv, $pdo_sqlsrv_src_class $shared_src_class, $ext_shared,,-I$pdo_cv_inc_path -std=c++11)
PHP_SUBST(PDO_SQLSRV_SHARED_LIBADD)
PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo)
PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1)
fi

View file

@ -1,54 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: config.w32
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no");
if( PHP_PDO_SQLSRV != "no" ) {
pdo_sqlsrv_src_class = " pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp ";
shared_src_class = " core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp ";
if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") &&
CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")&&
CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_PDO_SQLSRV", configure_module_dirname + "\\shared")) {
CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_PDO_SQLSRV_ODBC");
CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_PDO_SQLSRV_ODBC");
ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "pdo_sqlsrv" );
ADD_FLAG( "LDFLAGS_PDO_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/EHsc" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/GS" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/Zi" );
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" );
if (VCVERS >= 1913) {
ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload");
ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre");
} else if (VCVERS == 1900) {
var subver1900 = probe_binary(PHP_CL).substr(6);
if (subver1900 >= 24241) {
ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload");
ADD_FLAG('CFLAGS_PDO_SQLSRV', "/Qspectre");
}
}
ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo');
EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
} else {
WARNING("pdo-sqlsrv not enabled; libraries and headers not found");
}
}

View file

@ -1,2106 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// file: pdo_dbh.cpp
//
// Contents: Implements the PDO object for PDO_SQLSRV
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
#include <string>
#include <sstream>
typedef const zend_function_entry pdo_sqlsrv_function_entry;
// *** internal variables and constants ***
namespace {
const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;";
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=N'%s'";
const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( SEQUENCE_CURRENT_VALUE_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes
// List of PDO supported connection options.
namespace PDOConnOptionNames {
const char Server[] = "Server";
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char ColumnEncryption[] = "ColumnEncryption";
const char ConnectionPooling[] = "ConnectionPooling";
const char Language[] = "Language";
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
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";
const char QuotedId[] = "QuotedId";
const char TraceFile[] = "TraceFile";
const char TraceOn[] = "TraceOn";
const char TrustServerCertificate[] = "TrustServerCertificate";
const char TransactionIsolation[] = "TransactionIsolation";
const char TransparentNetworkIPResolution[] = "TransparentNetworkIPResolution";
const char WSID[] = "WSID";
const char ComputePool[] = "ComputePool";
const char HostNameInCertificate[] = "HostNameInCertificate";
}
enum PDO_CONN_OPTIONS {
PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
};
enum PDO_STMT_OPTIONS {
PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC,
PDO_STMT_OPTION_DIRECT_QUERY,
PDO_STMT_OPTION_CURSOR_SCROLL_TYPE,
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
PDO_STMT_OPTION_EMULATE_PREPARES,
PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE,
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE,
PDO_STMT_OPTION_FORMAT_DECIMALS,
PDO_STMT_OPTION_DECIMAL_PLACES,
PDO_STMT_OPTION_DATA_CLASSIFICATION
};
// List of all the statement options supported by this driver.
const stmt_option PDO_STMT_OPTS[] = {
{ NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr<stmt_option_query_timeout>( new stmt_option_query_timeout ) },
{ NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr<stmt_option_pdo_scrollable>( new stmt_option_pdo_scrollable ) },
{ NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr<stmt_option_encoding>( new stmt_option_encoding ) },
{ NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr<stmt_option_direct_query>( new stmt_option_direct_query ) },
{ NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr<stmt_option_cursor_scroll_type>( new stmt_option_cursor_scroll_type ) },
{ NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit ) },
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
{ NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals ) },
{ NULL, 0, PDO_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places ) },
{ NULL, 0, PDO_STMT_OPTION_DATA_CLASSIFICATION, std::unique_ptr<stmt_option_data_classification>( new stmt_option_data_classification ) },
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
};
// boolean connection string
struct pdo_bool_conn_str_func
{
static void func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str );
};
struct pdo_txn_isolation_conn_attr_func
{
static void func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ );
};
struct pdo_int_conn_str_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Wrong zval type for this keyword");
std::string val_str = Z_STRVAL_P( value );
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
}
};
struct pdo_encrypt_set_func
{
static void func(_In_ connection_option const* option, _Inout_ zval* value_z, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str)
{
SQLSRV_ASSERT(Z_TYPE_P(value_z) == IS_STRING, "Wrong zval type for this keyword");
std::string val_str = Z_STRVAL_P(value_z);
std::string whitespaces(" \t\f\v\n\r");
// Trim white spaces
std::size_t found = val_str.find_last_not_of(whitespaces);
if (found != std::string::npos)
val_str.erase(found + 1);
const char TRUE_VALUE_1[] = "true";
const char TRUE_VALUE_2[] = "1";
const char FALSE_VALUE_1[] = "false";
const char FALSE_VALUE_2[] = "0";
// For backward compatibility, convert true/1 to yes and false/0 to no
std::string attr;
if (!val_str.compare(TRUE_VALUE_1) || !val_str.compare(TRUE_VALUE_2)) {
attr = "yes";
} else if (!val_str.compare(FALSE_VALUE_1) || !val_str.compare(FALSE_VALUE_2)) {
attr = "no";
} else {
// simply pass the attribute value to ODBC driver
attr = val_str;
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str += attr;
conn_str += "};";
}
};
template <unsigned int Attr>
struct pdo_int_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." );
size_t val = static_cast<size_t>( atoi( Z_STRVAL_P( value )) );
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( val ), SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
};
template <unsigned int Attr>
struct pdo_bool_conn_attr_func {
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( core_str_zval_is_true( value )),
SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
};
// statement options related functions
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht,
_Inout_ zval** data );
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht );
} // namespace
// List of all connection options supported by this driver.
const connection_option PDO_CONN_OPTS[] = {
{
PDOConnOptionNames::Server,
sizeof( PDOConnOptionNames::Server ),
PDO_CONN_OPTION_SERVER,
NULL,
0,
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::APP,
sizeof( PDOConnOptionNames::APP ),
SQLSRV_CONN_OPTION_APP,
ODBCConnOptions::APP,
sizeof( ODBCConnOptions::APP ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::AccessToken,
sizeof( PDOConnOptionNames::AccessToken ),
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
ODBCConnOptions::AccessToken,
sizeof( ODBCConnOptions::AccessToken),
CONN_ATTR_STRING,
access_token_set_func::func
},
{
PDOConnOptionNames::ApplicationIntent,
sizeof( PDOConnOptionNames::ApplicationIntent ),
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
ODBCConnOptions::ApplicationIntent,
sizeof( ODBCConnOptions::ApplicationIntent ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::AttachDBFileName,
sizeof( PDOConnOptionNames::AttachDBFileName ),
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
ODBCConnOptions::AttachDBFileName,
sizeof( ODBCConnOptions::AttachDBFileName ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::Authentication,
sizeof( PDOConnOptionNames::Authentication ),
SQLSRV_CONN_OPTION_AUTHENTICATION,
ODBCConnOptions::Authentication,
sizeof( ODBCConnOptions::Authentication ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::ConnectionPooling,
sizeof( PDOConnOptionNames::ConnectionPooling ),
SQLSRV_CONN_OPTION_CONN_POOLING,
ODBCConnOptions::ConnectionPooling,
sizeof( ODBCConnOptions::ConnectionPooling ),
CONN_ATTR_BOOL,
conn_null_func::func
},
{
PDOConnOptionNames::Language,
sizeof( PDOConnOptionNames::Language ),
SQLSRV_CONN_OPTION_LANGUAGE,
ODBCConnOptions::Language,
sizeof( ODBCConnOptions::Language ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::Driver,
sizeof(PDOConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
PDOConnOptionNames::ColumnEncryption,
sizeof(PDOConnOptionNames::ColumnEncryption),
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
ODBCConnOptions::ColumnEncryption,
sizeof(ODBCConnOptions::ColumnEncryption),
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
PDOConnOptionNames::ConnectRetryCount,
sizeof( PDOConnOptionNames::ConnectRetryCount ),
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
ODBCConnOptions::ConnectRetryCount,
sizeof( ODBCConnOptions::ConnectRetryCount ),
CONN_ATTR_INT,
pdo_int_conn_str_func::func
},
{
PDOConnOptionNames::ConnectRetryInterval,
sizeof( PDOConnOptionNames::ConnectRetryInterval ),
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
ODBCConnOptions::ConnectRetryInterval,
sizeof( ODBCConnOptions::ConnectRetryInterval ),
CONN_ATTR_INT,
pdo_int_conn_str_func::func
},
{
PDOConnOptionNames::Database,
sizeof( PDOConnOptionNames::Database ),
SQLSRV_CONN_OPTION_DATABASE,
ODBCConnOptions::Database,
sizeof( ODBCConnOptions::Database ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::Encrypt,
sizeof( PDOConnOptionNames::Encrypt ),
SQLSRV_CONN_OPTION_ENCRYPT,
ODBCConnOptions::Encrypt,
sizeof( ODBCConnOptions::Encrypt ),
CONN_ATTR_MIXED,
pdo_encrypt_set_func::func
},
{
PDOConnOptionNames::Failover_Partner,
sizeof( PDOConnOptionNames::Failover_Partner ),
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
ODBCConnOptions::Failover_Partner,
sizeof( ODBCConnOptions::Failover_Partner ),
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 ),
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
ODBCConnOptions::LoginTimeout,
sizeof( ODBCConnOptions::LoginTimeout ),
CONN_ATTR_INT,
pdo_int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
},
{
PDOConnOptionNames::MARS_Option,
sizeof( PDOConnOptionNames::MARS_Option ),
SQLSRV_CONN_OPTION_MARS,
ODBCConnOptions::MARS_ODBC,
sizeof( ODBCConnOptions::MARS_ODBC ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::MultiSubnetFailover,
sizeof( PDOConnOptionNames::MultiSubnetFailover ),
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
ODBCConnOptions::MultiSubnetFailover,
sizeof( ODBCConnOptions::MultiSubnetFailover ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::QuotedId,
sizeof( PDOConnOptionNames::QuotedId ),
SQLSRV_CONN_OPTION_QUOTED_ID,
ODBCConnOptions::QuotedId,
sizeof( ODBCConnOptions::QuotedId ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::TraceFile,
sizeof( PDOConnOptionNames::TraceFile ),
SQLSRV_CONN_OPTION_TRACE_FILE,
ODBCConnOptions::TraceFile,
sizeof( ODBCConnOptions::TraceFile ),
CONN_ATTR_STRING,
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
},
{
PDOConnOptionNames::TraceOn,
sizeof( PDOConnOptionNames::TraceOn ),
SQLSRV_CONN_OPTION_TRACE_ON,
ODBCConnOptions::TraceOn,
sizeof( ODBCConnOptions::TraceOn ),
CONN_ATTR_BOOL,
pdo_bool_conn_attr_func<SQL_ATTR_TRACE>::func
},
{
PDOConnOptionNames::TransactionIsolation,
sizeof( PDOConnOptionNames::TransactionIsolation ),
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
ODBCConnOptions::TransactionIsolation,
sizeof( ODBCConnOptions::TransactionIsolation ),
CONN_ATTR_INT,
pdo_txn_isolation_conn_attr_func::func
},
{
PDOConnOptionNames::TrustServerCertificate,
sizeof( PDOConnOptionNames::TrustServerCertificate ),
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
ODBCConnOptions::TrustServerCertificate,
sizeof( ODBCConnOptions::TrustServerCertificate ),
CONN_ATTR_BOOL,
pdo_bool_conn_str_func::func
},
{
PDOConnOptionNames::TransparentNetworkIPResolution,
sizeof(PDOConnOptionNames::TransparentNetworkIPResolution),
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
ODBCConnOptions::TransparentNetworkIPResolution,
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::WSID,
sizeof( PDOConnOptionNames::WSID ),
SQLSRV_CONN_OPTION_WSID,
ODBCConnOptions::WSID,
sizeof( ODBCConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::ComputePool,
sizeof(PDOConnOptionNames::ComputePool),
SQLSRV_CONN_OPTION_COMPUTE_POOL,
ODBCConnOptions::ComputePool,
sizeof(ODBCConnOptions::ComputePool),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::HostNameInCertificate,
sizeof(PDOConnOptionNames::HostNameInCertificate),
SQLSRV_CONN_OPTION_HOSTNAME_IN_CERT,
ODBCConnOptions::HostNameInCertificate,
sizeof(ODBCConnOptions::HostNameInCertificate),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
};
#if PHP_VERSION_ID < 80100
// close the connection
int pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh);
// execute queries
int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options);
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len);
// quote a string, meaning put quotes around it and escape any quotes within it
int pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_reads_(unquotedlen) const char* unquoted, _In_ size_t unquotedlen, _Outptr_result_buffer_(*quotedlen) char **quoted, _Out_ size_t* quotedlen,
enum pdo_param_type paramtype);
// transaction support functions
int pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh);
int pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh);
int pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh);
// attribute functions
int pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val);
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value);
// return the last id generated by an executed SQL statement
char * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len);
// return more information
int pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info);
#else
// close the connection
void pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh);
// execute queries
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql,
_Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options);
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *sql);
// quote a string, meaning put quotes around it and escape any quotes within it
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype);
// transaction support functions
bool pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh);
bool pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh);
bool pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh);
// attribute functions
bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val);
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value);
// return the last id generated by an executed SQL statement
zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *name);
// return more information
void pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info);
#endif
// additional methods are supported in this function
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind );
struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = {
pdo_sqlsrv_dbh_close,
pdo_sqlsrv_dbh_prepare,
pdo_sqlsrv_dbh_do,
pdo_sqlsrv_dbh_quote,
pdo_sqlsrv_dbh_begin,
pdo_sqlsrv_dbh_commit,
pdo_sqlsrv_dbh_rollback,
pdo_sqlsrv_dbh_set_attr,
pdo_sqlsrv_dbh_last_id,
pdo_sqlsrv_dbh_return_error,
pdo_sqlsrv_dbh_get_attr,
NULL, // check liveness not implemented
pdo_sqlsrv_get_driver_methods,
NULL, // request shutdown not implemented
#if PHP_VERSION_ID < 80100
NULL // in transaction not implemented
};
#else
NULL, // in transaction not implemented
NULL // get_gc not implemented
};
#endif
// log a function entry point
#define PDO_LOG_DBH_ENTRY \
{ \
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data ); \
if (driver_dbh != NULL) driver_dbh->set_func(__FUNCTION__); \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}
// constructor for the internal object for connections
pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ) :
sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 ),
stmts( NULL ),
direct_query( false ),
query_timeout( QUERY_TIMEOUT_INVALID ),
client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )),
fetch_numeric( false ),
fetch_datetime( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED),
emulate_prepare(false)
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." );
}
}
// pdo_sqlsrv_db_handle_factory
// Maps to PDO::__construct.
// Factory method called by the PDO driver manager to create a SQLSRV PDO connection.
// Does the following things:
// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION.
// (If an error occurs in this function, the PDO specification mandates that
// an exception be thrown, regardless of the error mode setting.)
// 2. Processes the driver options.
// 3. Creates a core_conn object by calling core_sqlsrv_connect.
// 4. Restores the previous error mode on success.
// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory
// Parameters:
// dbh - The PDO managed structure for the connection.
// driver_options - A HashTable (within the zval) of options to use when creating the connection.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options)
{
PDO_LOG_DBH_ENTRY;
hash_auto_ptr pdo_conn_options_ht;
pdo_error_mode prev_err_mode = dbh->error_mode;
// must be done in all cases so that even a failed connection can query the
// object for errors.
dbh->methods = &pdo_sqlsrv_dbh_methods;
dbh->driver_data = NULL;
zval* temp_server_z = NULL;
sqlsrv_malloc_auto_ptr<conn_string_parser> dsn_parser;
zval server_z;
ZVAL_UNDEF( &server_z );
try {
// no matter what the error mode, we want exceptions thrown if the connection fails
// to happen (per the PDO spec)
dbh->error_mode = PDO_ERRMODE_EXCEPTION;
g_pdo_henv_cp->set_driver( dbh );
g_pdo_henv_ncp->set_driver( dbh );
CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_pdo_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) {
throw core::CoreException();
}
// throws PDOException if the ATTR_PERSISTENT is in connection options
CHECK_CUSTOM_ERROR( dbh->is_persistent, *g_pdo_henv_cp, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ) {
dbh->refcount--;
throw pdo::PDOException();
}
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_conn_options_ht );
core::sqlsrv_zend_hash_init( *g_pdo_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
// Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error.
dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_pdo_henv_cp, dbh->data_source,
static_cast<int>( dbh->data_source_len ), pdo_conn_options_ht );
dsn_parser->parse_conn_string();
// Extract the server name
temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER );
CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_pdo_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) {
throw pdo::PDOException();
}
server_z = *temp_server_z;
// Add a reference to the option value since we are deleting it from the hashtable
zval_add_ref( &server_z );
zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER );
sqlsrv_conn* conn = core_sqlsrv_connect( *g_pdo_henv_cp, *g_pdo_henv_ncp, core::allocate_conn<pdo_sqlsrv_dbh>, Z_STRVAL( server_z ),
dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error,
PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" );
// Free the string in server_z after being used
zend_string_release( Z_STR( server_z ));
SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." );
// set the driver_data and methods to complete creation of the PDO object
dbh->driver_data = conn;
dbh->error_mode = prev_err_mode; // reset the error mode
dbh->alloc_own_columns = 1; // we do our own memory management for columns
dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types
}
catch( core::CoreException& ) {
if ( Z_TYPE( server_z ) == IS_STRING ) {
zend_string_release( Z_STR( server_z ));
}
dbh->error_mode = prev_err_mode; // reset the error mode
g_pdo_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" );
}
return 1;
}
// pdo_sqlsrv_dbh_close
// Maps to PDO::__destruct.
// Called when a PDO object is to be destroyed.
// By the time this function is called, PDO has already made sure that
// all statements are disposed and the PDO object is the last item destroyed.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// Always returns 1 for success. (for PHP_VERSION_ID < 80100)
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh )
#else
void pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh)
#endif
{
LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" );
// if the connection didn't complete properly, driver_data isn't initialized.
if( dbh->driver_data == NULL ) {
#if PHP_VERSION_ID < 80100
return 1;
#else
return;
#endif
}
PDO_RESET_DBH_ERROR;
// call the core layer close
core_sqlsrv_close( reinterpret_cast<sqlsrv_conn*>( dbh->driver_data ) );
dbh->driver_data = NULL;
#if PHP_VERSION_ID < 80100
// always return success that the connection is closed
return 1;
#endif
}
// pdo_sqlsrv_dbh_prepare
// Called by PDO::prepare and PDOStatement::__construct.
// Creates a statement and prepares it for execution by PDO
// Paramters:
// dbh - The PDO managed connection object.
// sql - SQL query to be prepared.
// sql_len - Length of the sql query
// stmt - The PDO managed statement object.
// driver_options - User provided list of statement options.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#else
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
hash_auto_ptr pdo_stmt_options_ht;
sqlsrv_malloc_auto_ptr<char> sql_rewrite;
size_t sql_rewrite_len = 0;
sqlsrv_malloc_auto_ptr<pdo_sqlsrv_stmt> driver_stmt;
hash_auto_ptr placeholders;
sqlsrv_malloc_auto_ptr<sql_string_parser> sql_parser;
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null");
try {
// assign the methods for the statement object. This is necessary even if the
// statement fails so the user can retrieve the error information.
stmt->methods = &pdo_sqlsrv_stmt_methods;
// if not emulate_prepare, we support parameterized queries with ?, not names
stmt->supports_placeholders = (driver_dbh->emulate_prepare) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; // the statement options may override this later
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_stmt_options_ht );
core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
// Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error.
validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht );
driver_stmt = static_cast<pdo_sqlsrv_stmt*>( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>,
pdo_stmt_options_ht, PDO_STMT_OPTS,
pdo_sqlsrv_handle_stmt_error, stmt ));
// if the user didn't set anything in the prepare options, then set the buffer limit
// to the value set on the connection.
if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) {
driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size;
}
#if PHP_VERSION_ID >= 80100
zend_string* sql_rewrite_zstr = NULL;
const char* sql = ZSTR_VAL(sql_zstr);
size_t sql_len = ZSTR_LEN(sql_zstr);
#endif
// rewrite named parameters in the query to positional parameters if we aren't letting PDO do the
// parameter substitution for us
if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
// rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named
// parameters for consistency with the PDO MySQL and PDO ODBC drivers.
#if PHP_VERSION_ID < 80100
int zr = pdo_parse_params( stmt, const_cast<char*>( sql ), sql_len, &sql_rewrite, &sql_rewrite_len );
CHECK_ZEND_ERROR(zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
throw core::CoreException();
}
// if parameter substitution happened, use that query instead of the original
if (sql_rewrite != 0) {
sql = sql_rewrite;
sql_len = sql_rewrite_len;
}
#else
int zr = pdo_parse_params(stmt, sql_zstr, &sql_rewrite_zstr);
CHECK_ZEND_ERROR(zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
throw core::CoreException();
}
// if parameter substitution happened, use that query instead of the original
if (sql_rewrite_zstr != NULL) {
sql = ZSTR_VAL(sql_rewrite_zstr);
sql_len = ZSTR_LEN(sql_rewrite_zstr);
}
#endif
}
if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
core_sqlsrv_prepare(driver_stmt, sql, sql_len);
}
else if( driver_stmt->direct_query ) {
if( driver_stmt->direct_query_subst_string ) {
// we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme
// and we use estrdup below to allocate the new string, which uses emalloc
efree( reinterpret_cast<void*>( const_cast<char*>( driver_stmt->direct_query_subst_string )));
}
driver_stmt->direct_query_subst_string = estrdup( sql );
driver_stmt->direct_query_subst_string_len = sql_len;
}
#if PHP_VERSION_ID >= 80100
if (sql_rewrite_zstr != NULL) {
zend_string_release(sql_rewrite_zstr);
}
#endif
// else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
// set to the substituted query
if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
// parse placeholders in the sql query into the placeholders ht
ALLOC_HASHTABLE( placeholders );
core::sqlsrv_zend_hash_init(*driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */);
#if PHP_VERSION_ID < 80100
sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, stmt->query_string,
static_cast<int>(stmt->query_stringlen), placeholders);
#else
sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, ZSTR_VAL(stmt->query_string),
ZSTR_LEN(stmt->query_string), placeholders);
#endif
sql_parser->parse_sql_string();
driver_stmt->placeholders = placeholders;
placeholders.transferred();
}
stmt->driver_data = driver_stmt;
driver_stmt.transferred();
}
// everything is cleaned up by this point
// catch everything so the exception doesn't spill into the calling PDO code
catch( core::CoreException& ) {
if( driver_stmt ) {
driver_stmt->~pdo_sqlsrv_stmt();
}
// in the event that the statement caused an error that was copied to the connection, update the
// connection with the error's SQLSTATE.
if( driver_dbh->last_error() ) {
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
reinterpret_cast<const char*>( driver_dbh->last_error()->sqlstate ));
}
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// catch any errant exception and die
catch(...) {
DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." );
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
// pdo_sqlsrv_dbh_do
// Maps to PDO::exec.
// Execute a SQL statement, such as an insert, update or delete, and return
// the number of rows affected.
// Parameters:
// dbh - the PDO connection object, which contains the ODBC handle
// sql - the query to execute
// sql_len - length of sql query
// Return
// # of rows affected, -1 for an error.
#if PHP_VERSION_ID < 80100
zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len )
#else
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *sql)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> driver_stmt;
SQLLEN rows = 0;
// verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong
// thing to happen here.
SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN ));
try {
SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_do: driver_data object was NULL.");
// temp PDO statement used for error handling if something happens
pdo_stmt_t temp_stmt;
temp_stmt.dbh = dbh;
// allocate a full driver statement to take advantage of the error handling
driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>, NULL /*options_ht*/,
NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt );
driver_stmt->set_func( __FUNCTION__ );
#if PHP_VERSION_ID < 80100
SQLRETURN execReturn = core_sqlsrv_execute(driver_stmt, sql, static_cast<int>(sql_len));
#else
SQLRETURN execReturn = core_sqlsrv_execute(driver_stmt, ZSTR_VAL(sql), ZSTR_LEN(sql));
#endif
// since the user can give us a compound statement, we return the row count for the last set, and since the row count
// isn't guaranteed to be valid until all the results have been fetched, we fetch them all first.
if ( execReturn != SQL_NO_DATA && core_sqlsrv_has_any_result( driver_stmt )) {
SQLRETURN r = SQL_SUCCESS;
do {
rows = core::SQLRowCount( driver_stmt );
r = core::SQLMoreResults( driver_stmt );
} while ( r != SQL_NO_DATA );
}
// returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases
// naturally, so we override that here with no rows returned.
if( rows == -1 ) {
rows = 0;
}
}
catch( core::CoreException& ) {
// copy any errors on the statement to the connection so that the user sees them, since the statement is released
// before this method returns
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
reinterpret_cast<const char*>( driver_stmt->last_error()->sqlstate ));
driver_dbh->set_last_error( driver_stmt->last_error() );
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
return -1;
}
catch( ... ) {
DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." );
}
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
return rows;
}
// transaction support functions
// pdo_sqlsrv_dbh_begin
// Maps to PDO::beginTransaction.
// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn
// flag is maintained by PDO so we dont have to worry about it.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success. (if PHP_VERSION_ID < 80100)
// Return true if currently inside a transaction, false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
try {
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" );
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" );
DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" );
core_sqlsrv_begin_transaction( driver_conn );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_commit
// Maps to PDO::commit.
// Commits a transaction. Returns the connection to auto-commit mode.
// PDO throws error if PDO::commit is called on a connection that is not in an active
// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have
// to worry about it here.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success. (if PHP_VERSION_ID < 80100)
// Return true for success and false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
try {
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" );
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" );
DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" );
core_sqlsrv_commit( driver_conn );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_rollback
// Maps to PDO::rollback.
// Rolls back a transaction. Returns the connection in auto-commit mode.
// PDO throws error if PDO::rollBack is called on a connection that is not in an active
// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have
// to worry about it here.
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success. (if PHP_VERSION_ID < 80100)
// Return true for success and false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
try {
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" );
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" );
DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" );
core_sqlsrv_rollback( driver_conn );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_set_attr
// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object.
// PDO driver manager calls this function directly after calling the factory
// method for PDO, for any attribute which is specified in the PDO constructor.
// Parameters:
// dbh - The PDO connection object maintained by PDO.
// attr - The attribute to be set.
// val - The value of the attribute to be set.
// Return:
// 0 for failure, 1 for success. (if PHP_VERSION_ID < 80100)
// Return true on success and false in case of failure
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val)
#else
bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_set_attr: driver_data object was NULL.");
try {
switch( attr ) {
case SQLSRV_ATTR_ENCODING:
{
zend_long attr_value;
if( Z_TYPE_P( val ) != IS_LONG ) {
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING );
}
attr_value = Z_LVAL_P( val );
switch( attr_value ) {
case SQLSRV_ENCODING_DEFAULT:
// when default is applied to a connection, that means use UTF-8 encoding
driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 );
break;
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_UTF8:
driver_dbh->set_encoding( static_cast<SQLSRV_ENCODING>( attr_value ));
break;
default:
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING );
break;
}
}
break;
case SQLSRV_ATTR_DIRECT_QUERY:
driver_dbh->direct_query = zend_is_true(val);
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) {
convert_to_string( val );
THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val ));
}
driver_dbh->query_timeout = static_cast<long>( Z_LVAL_P( val ) );
break;
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) {
convert_to_string( val );
THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val ));
}
driver_dbh->client_buffer_max_size = Z_LVAL_P( val );
break;
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
driver_dbh->fetch_numeric = zend_is_true(val);
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_dbh->fetch_datetime = zend_is_true(val);
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
driver_dbh->format_decimals = zend_is_true(val);
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
{
// first check if the input is an integer
if (Z_TYPE_P(val) != IS_LONG) {
THROW_PDO_ERROR(driver_dbh, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
}
zend_long decimal_places = Z_LVAL_P(val);
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
// ignore decimal_places as this is out of range
decimal_places = NO_CHANGE_DECIMAL_PLACES;
}
driver_dbh->decimal_places = static_cast<short>(decimal_places);
}
break;
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
if (Z_TYPE_P(val) != IS_LONG) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
zend_long value = Z_LVAL_P(val);
if (value == PDO_PARAM_STR_NATL) {
driver_dbh->use_national_characters = 1;
}
else if (value == PDO_PARAM_STR_CHAR) {
driver_dbh->use_national_characters = 0;
}
else {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
}
break;
#endif
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
case PDO_ATTR_PREFETCH:
case PDO_ATTR_MAX_COLUMN_LEN:
case PDO_ATTR_CURSOR_NAME:
case PDO_ATTR_AUTOCOMMIT:
case PDO_ATTR_PERSISTENT:
case PDO_ATTR_TIMEOUT:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR );
}
// Read-only
case PDO_ATTR_SERVER_VERSION:
case PDO_ATTR_SERVER_INFO:
case PDO_ATTR_CLIENT_VERSION:
case PDO_ATTR_DRIVER_NAME:
case PDO_ATTR_CONNECTION_STATUS:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR );
}
case PDO_ATTR_EMULATE_PREPARES:
{
driver_dbh->emulate_prepare = zend_is_true(val);
if (driver_dbh->emulate_prepare && driver_dbh->ce_option.enabled) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
}
}
break;
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
}
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
break;
}
}
}
catch( pdo::PDOException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
// pdo_sqlsrv_dbh_get_attr
// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object.
// Parameters:
// dbh - The PDO connection object maintained by PDO.
// attr - The attribute to get.
// return_value - zval in which to return the attribute value.
// Return:
// 0 for failure, 1 for success. (if PHP_VERSION_ID < 80100)
// There are 3 return states:
// -1 for errors while retrieving a valid attribute
// 0 for attempting to retrieve an attribute which is not supported by the driver
// any other value for success, *return_value must be set to the attribute value
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value)
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_get_attr: driver_data object was NULL.");
try {
switch( attr ) {
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
case PDO_ATTR_PREFETCH:
case PDO_ATTR_MAX_COLUMN_LEN:
case PDO_ATTR_CURSOR_NAME:
case PDO_ATTR_AUTOCOMMIT:
case PDO_ATTR_TIMEOUT:
{
#if PHP_VERSION_ID < 80100
// PDO does not throw "not supported" error message for these attributes.
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR);
#else
return 0;
#endif
}
case PDO_ATTR_EMULATE_PREPARES:
{
ZVAL_BOOL(return_value, driver_dbh->emulate_prepare);
break;
}
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
}
case PDO_ATTR_STRINGIFY_FETCHES:
{
// For this attribute, if we dont set the return_value than PDO returns NULL.
ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) );
break;
}
case PDO_ATTR_SERVER_INFO:
{
core_sqlsrv_get_server_info( driver_dbh, return_value );
break;
}
case PDO_ATTR_SERVER_VERSION:
{
core_sqlsrv_get_server_version( driver_dbh, return_value );
break;
}
case PDO_ATTR_CLIENT_VERSION:
{
core_sqlsrv_get_client_info( driver_dbh, return_value );
//Add the PDO SQLSRV driver's file version
//Declarations below eliminate compiler warnings about string constant to char* conversions
const char* extver = "ExtensionVer";
std::string filever = VER_FILEVERSION_STR;
add_assoc_string(return_value, extver, &filever[0]);
break;
}
case SQLSRV_ATTR_ENCODING:
{
ZVAL_LONG( return_value, driver_dbh->encoding() );
break;
}
case SQLSRV_ATTR_QUERY_TIMEOUT:
{
ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout ));
break;
}
case SQLSRV_ATTR_DIRECT_QUERY:
{
ZVAL_BOOL( return_value, driver_dbh->direct_query );
break;
}
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
{
ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size );
break;
}
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
{
ZVAL_BOOL( return_value, driver_dbh->fetch_numeric );
break;
}
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
{
ZVAL_BOOL( return_value, driver_dbh->fetch_datetime );
break;
}
case SQLSRV_ATTR_FORMAT_DECIMALS:
{
ZVAL_BOOL( return_value, driver_dbh->format_decimals );
break;
}
case SQLSRV_ATTR_DECIMAL_PLACES:
{
ZVAL_LONG( return_value, driver_dbh->decimal_places );
break;
}
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
ZVAL_LONG(return_value, (driver_dbh->use_national_characters == 0) ? PDO_PARAM_STR_CHAR : PDO_PARAM_STR_NATL);
break;
}
#endif
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
break;
}
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return 1;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return -1;
#endif
}
}
// Called by PDO::errorInfo and PDOStatement::errorInfo.
// Returns the error info.
// Parameters:
// dbh - The PDO managed connection object.
// stmt - The PDO managed statement object.
// info - zval in which to return the error info.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info)
#else
void pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info)
#endif
{
SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." );
sqlsrv_error* ctx_error = NULL;
if( stmt ) {
ctx_error = static_cast<sqlsrv_stmt*>( stmt->driver_data )->last_error();
}
else {
ctx_error = static_cast<sqlsrv_conn*>( dbh->driver_data )->last_error();
}
pdo_sqlsrv_retrieve_context_error( ctx_error, info );
#if PHP_VERSION_ID < 80100
return 1;
#endif
}
// pdo_sqlsrv_dbh_last_id
// Maps to PDO::lastInsertId.
// Returns the last id generated by an executed SQL statement
// Parameters:
// dbh - The PDO managed connection object.
// name - Table name.
// len - Length of the name.
// Return:
// Returns the last insert id as a string. (if PHP_VERSION_ID < 80100)
// Returning NULL indicates an error condition. The input "name" MIGHT be NULL
#if PHP_VERSION_ID < 80100
char * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len)
#else
zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *name)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
// turn off any error handling for last_id
pdo_error_mode prev_err_mode = dbh->error_mode;
dbh->error_mode = PDO_ERRMODE_SILENT;
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> driver_stmt;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_last_id: driver_data object was NULL." );
char idSTR[LAST_INSERT_ID_BUFF_LEN] = { '\0' };
char* str = NULL;
SQLLEN cbID = 0;
try {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len;
if (name == NULL) {
wsql_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_CHAR, LAST_INSERT_ID_QUERY, sizeof(LAST_INSERT_ID_QUERY), &wsql_len);
} else {
char buffer[LAST_INSERT_ID_QUERY_MAX_LEN] = { '\0' };
#if PHP_VERSION_ID < 80100
snprintf(buffer, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, name);
#else
snprintf(buffer, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, ZSTR_VAL(name));
#endif
wsql_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_CHAR, buffer, sizeof(buffer), &wsql_len);
}
CHECK_CUSTOM_ERROR(wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
// temp PDO statement used for error handling if something happens
pdo_stmt_t temp_stmt;
temp_stmt.dbh = dbh;
// allocate a full driver statement to take advantage of the error handling
driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt );
driver_stmt->set_func( __FUNCTION__ );
// execute the last insert id query
core::SQLExecDirectW( driver_stmt, wsql_string );
core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 );
SQLRETURN r = core::SQLGetData(driver_stmt, 1, SQL_C_CHAR, idSTR, LAST_INSERT_ID_BUFF_LEN, &cbID, false);
CHECK_CUSTOM_ERROR((!SQL_SUCCEEDED(r) || cbID == SQL_NULL_DATA || cbID == SQL_NO_TOTAL), driver_stmt,
PDO_SQLSRV_ERROR_LAST_INSERT_ID) {
throw core::CoreException();
}
driver_stmt->~sqlsrv_stmt();
} catch( core::CoreException& ) {
// restore error handling to its previous mode
dbh->error_mode = prev_err_mode;
// copy any errors on the statement to the connection so that the user sees them, since the statement is released
// before this method returns
strcpy_s( dbh->error_code, sizeof( dbh->error_code ),
reinterpret_cast<const char*>( driver_stmt->last_error()->sqlstate ));
driver_dbh->set_last_error( driver_stmt->last_error() );
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
#if PHP_VERSION_ID < 80100
*len = 0;
str = reinterpret_cast<char*>(sqlsrv_malloc(0, sizeof(char), 1)); // return an empty string with a null terminator
str[0] = '\0';
return str;
#else
return ZSTR_EMPTY_ALLOC();
#endif
}
// restore error handling to its previous mode
dbh->error_mode = prev_err_mode;
// copy the last ID string and return it
str = reinterpret_cast<char*>(sqlsrv_malloc(cbID, sizeof(char), 1)); // include space for null terminator
strcpy_s(str, cbID + 1, idSTR);
#if PHP_VERSION_ID < 80100
*len = static_cast<size_t>(cbID);
return str;
#else
zend_string *zstr = zend_string_init(str, cbID, 0);
sqlsrv_free(str);
return zstr;
#endif
}
// pdo_sqlsrv_dbh_quote
// Maps to PDO::quote. As the name says, this function quotes a string.
// Always returns a valid string unless memory allocation fails.
// Parameters:
// dbh - The PDO managed connection object.
// unquoted - The unquoted string to be quoted.
// unquoted_len - Length of the unquoted string.
// quoted - Buffer for output string.
// quoted_len - Length of the output string.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len,
enum pdo_param_type paramtype )
#else
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
bool use_national_char_set = false;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>(dbh->driver_data);
SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL.");
// get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
// 1. PDO::quote() - object name is PDO
// 2. PDOStatement::execute() - object name is PDOStatement
zend_execute_data* execute_data = EG( current_execute_data );
zval *object = getThis();
// iterate through parents to find "PDOStatement"
bool is_statement = false;
if ( object ) {
zend_class_entry* curr_class = ( Z_OBJ_P( object ))->ce;
while ( curr_class != NULL ) {
if ( strcmp( reinterpret_cast<const char*>( curr_class->name->val ), "PDOStatement" ) == 0 ) {
is_statement = true;
break;
}
curr_class = curr_class->parent;
}
}
// only change the encoding if quote is called from the statement level (which should only be called when a statement
// is prepared with emulate prepared on)
if (is_statement) {
pdo_stmt_t *stmt = Z_PDO_STMT_P(object);
SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_dbh_quote: stmt object was null");
// set the encoding to be the encoding of the statement otherwise set to be the encoding of the dbh
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null");
encoding = driver_stmt->encoding();
if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) {
pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>(stmt->driver_data);
encoding = stmt_driver_dbh->encoding();
}
// get the placeholder at the current position in driver_stmt->placeholders ht
// Normally it's not a good idea to alter the internal pointer in a hashed array
// (see pull request 634 on GitHub) but in this case this is for internal use only
zval* placeholder = NULL;
if ((placeholder = zend_hash_get_current_data(driver_stmt->placeholders)) != NULL && zend_hash_move_forward(driver_stmt->placeholders) == SUCCESS && stmt->bound_params != NULL) {
pdo_bound_param_data* param = NULL;
if (Z_TYPE_P(placeholder) == IS_STRING) {
param = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_params, Z_STR_P(placeholder)));
}
else if (Z_TYPE_P(placeholder) == IS_LONG) {
param = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_params, Z_LVAL_P(placeholder)));
}
if (NULL != param) {
SQLSRV_ENCODING param_encoding = static_cast<SQLSRV_ENCODING>(Z_LVAL(param->driver_params));
if (param_encoding != SQLSRV_ENCODING_INVALID) {
encoding = param_encoding;
}
}
}
}
use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8);
#if PHP_VERSION_ID >= 70200
if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
use_national_char_set = true;
}
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
use_national_char_set = false;
}
#endif
if (encoding == SQLSRV_ENCODING_BINARY) {
#if PHP_VERSION_ID < 80100
*quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result
*quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator
memset(*quoted, '\0', *quoted_len + 1);
unsigned int pos = 0;
(*quoted)[pos++] = '0';
(*quoted)[pos++] = 'x';
for (size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index) {
// On success, snprintf returns the total number of characters written
// On failure, a negative number is returned
// The generated string has a length of at most len - 1, so
// len is 3 (2 hex digits + 1)
// Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
int n = snprintf((char*)(*quoted + pos), 3, "%02X", unquoted[index] & 0x000000FF);
if (n < 0) {
// Something went wrong, simply return 0 (failure)
return 0;
}
pos += 2;
}
return 1;
#else
size_t unquoted_len = ZSTR_LEN(unquoted);
const char *unquoted_str = ZSTR_VAL(unquoted);
sqlsrv_malloc_auto_ptr<char> quoted;
size_t quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result
quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1)); // include space for null terminator
memset(quoted, '\0', quoted_len + 1);
unsigned int pos = 0;
quoted[pos++] = '0';
quoted[pos++] = 'x';
char *p = quoted;
for (size_t index = 0; index < unquoted_len && unquoted_str[index] != '\0'; ++index) {
// On success, snprintf returns the total number of characters written
// On failure, a negative number is returned
// The generated string has a length of at most len - 1, so
// len is 3 (2 hex digits + 1)
// Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
int n = snprintf((char*)(p + pos), 3, "%02X", unquoted_str[index] & 0x000000FF);
if (n < 0) {
// Something went wrong, simply return NULL (failure)
return NULL;
}
pos += 2;
}
zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
return zstr;
#endif
}
else {
// The minimum number of single quotes needed is 2 -- the initial start and end quotes
// Add the letter N before the initial quote if the encoding is UTF8
int quotes_needed = (use_national_char_set) ? 3 : 2;
char c = '\'';
#if PHP_VERSION_ID < 80100
std::string tmp_str(unquoted, unquoted_len); // Copy all unquoted_len characters from unquoted
#else
size_t unquoted_len = ZSTR_LEN(unquoted);
const char *unquoted_str = ZSTR_VAL(unquoted);
std::string tmp_str(unquoted_str, unquoted_len); // Copy all unquoted_len characters from unquoted
#endif
std::size_t found = tmp_str.find(c); // Find the first single quote
while (found != std::string::npos) {
tmp_str.insert(found + 1, 1, c); // Insert an additional single quote
found = tmp_str.find(c, found + 2); // Find the next single quote
}
size_t len = tmp_str.length();
#if PHP_VERSION_ID < 80100
*quoted_len = quotes_needed + len; // The new length should be number of quotes plus the length of tmp_str
*quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator
memset(*quoted, '\0', *quoted_len + 1);
char *p = *quoted;
#else
sqlsrv_malloc_auto_ptr<char> quoted;
size_t quoted_len = quotes_needed + len; // length returned to the caller should not account for null terminator
quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1)); // include space for null terminator
memset(quoted, '\0', quoted_len + 1);
char *p = quoted;
#endif
size_t pos = 0;
if (use_national_char_set) { // Insert the letter N if the encoding is UTF8
*(p + (pos++)) = 'N';
}
*(p + (pos++)) = c; // Add the initial quote
tmp_str.copy(p + pos, len, 0); // Copy tmp_str to *quoted
pos += len;
*(p + pos) = c; // Add the end quote
#if PHP_VERSION_ID < 80100
return 1;
#else
zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
return zstr;
#endif
}
}
// This method is not implemented by this driver.
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
// As per documentation, simply return false if the method does not exist
// https://www.php.net/manual/en/function.is-callable.php
// But user can call PDO::errorInfo() to check the error message if necessary
CHECK_CUSTOM_WARNING_AS_ERROR(true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED);
return NULL; // return NULL for PDO to take care of the rest
}
namespace {
// Maps the PDO driver specific statement option/attribute constants to the core layer
// statement option/attribute constants.
void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht,
_Inout_ zval* data)
{
zend_ulong option_key = -1;
switch (key) {
case PDO_ATTR_CURSOR:
option_key = SQLSRV_STMT_OPTION_SCROLLABLE;
break;
case SQLSRV_ATTR_ENCODING:
option_key = PDO_STMT_OPTION_ENCODING;
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT;
break;
case PDO_ATTR_STATEMENT_CLASS:
break;
case SQLSRV_ATTR_DIRECT_QUERY:
option_key = PDO_STMT_OPTION_DIRECT_QUERY;
break;
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE;
break;
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE;
break;
case PDO_ATTR_EMULATE_PREPARES:
option_key = PDO_STMT_OPTION_EMULATE_PREPARES;
break;
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE;
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
option_key = PDO_STMT_OPTION_FORMAT_DECIMALS;
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
option_key = PDO_STMT_OPTION_DECIMAL_PLACES;
break;
case SQLSRV_ATTR_DATA_CLASSIFICATION:
option_key = PDO_STMT_OPTION_DATA_CLASSIFICATION;
break;
default:
CHECK_CUSTOM_ERROR(true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION)
{
throw core::CoreException();
}
break;
}
// if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it
if (option_key != -1) {
zval_add_ref(data);
core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data);
}
}
// validate_stmt_options
// Iterates through the list of statement options provided by the user and validates them
// against the list of statement options provided by this driver. After validation
// creates a Hashtable of statement options to be sent to the core layer for processing.
// Parameters:
// ctx - The current context.
// stmt_options - The user provided list of statement options.
// pdo_stmt_options_ht - Output hashtable of statement options.
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht )
{
try {
if( stmt_options ) {
HashTable* options_ht = Z_ARRVAL_P( stmt_options );
size_t int_key = -1;
zend_string *key = NULL;
zval* data = NULL;
ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) {
int type = HASH_KEY_NON_EXISTENT;
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
throw core::CoreException();
}
add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data );
} ZEND_HASH_FOREACH_END();
}
}
catch( core::CoreException& ) {
throw;
}
}
void pdo_bool_conn_str_func::func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
char const* val_str = "no";
if( core_str_zval_is_true( value ) ) {
val_str = "yes";
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
}
void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn,
std::string& /*conn_str*/ )
{
try {
SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." );
const char* val = Z_STRVAL_P( value_z );
size_t val_len = Z_STRLEN_P( value_z );
zend_long out_val = SQL_TXN_READ_COMMITTED;
// READ_COMMITTED
if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 )
&& !strcasecmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) {
out_val = SQL_TXN_READ_COMMITTED;
}
// READ_UNCOMMITTED
else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 )
&& !strcasecmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) {
out_val = SQL_TXN_READ_UNCOMMITTED;
}
// REPEATABLE_READ
else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 )
&& !strcasecmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) {
out_val = SQL_TXN_REPEATABLE_READ;
}
// SERIALIZABLE
else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 )
&& !strcasecmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) {
out_val = SQL_TXN_SERIALIZABLE;
}
// SNAPSHOT
else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 )
&& !strcasecmp( val, PDOTxnIsolationValues::SNAPSHOT ))) {
out_val = SQL_TXN_SS_SNAPSHOT;
}
else {
CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) {
throw core::CoreException();
}
}
core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast<SQLPOINTER>( out_val ), SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
} // namespace

View file

@ -1,346 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_init.cpp
//
// Contents: initialization routines for PDO_SQLSRV
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
#ifdef COMPILE_DL_PDO_SQLSRV
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE();
#endif
ZEND_GET_MODULE(g_pdo_sqlsrv)
#endif
extern "C" {
ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv);
}
// module global variables (initialized in minit and freed in mshutdown)
HashTable* g_pdo_errors_ht = NULL;
// henv context for creating connections
sqlsrv_context* g_pdo_henv_cp;
sqlsrv_context* g_pdo_henv_ncp;
namespace {
pdo_driver_t pdo_sqlsrv_driver = {
PDO_DRIVER_HEADER(sqlsrv),
pdo_sqlsrv_db_handle_factory
};
// functions to register SQLSRV constants with the PDO class
// (It's in all CAPS so it looks like the Zend macros that do similar work)
void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( _In_z_ char const* name, _In_ long value );
void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value );
struct sqlsrv_attr_pdo_constant {
const char *name;
int value;
};
// forward decl for table
extern sqlsrv_attr_pdo_constant pdo_attr_constants[];
}
static zend_module_dep pdo_sqlsrv_depends[] = {
ZEND_MOD_REQUIRED("pdo")
{NULL, NULL, NULL}
};
// argument info structures for functions, arranged alphabetically.
// see zend_API.h in the PHP sources for more information about these macros
// function table with associated arginfo structures
zend_function_entry pdo_sqlsrv_functions[] = {
{NULL, NULL, NULL} // no functions directly defined by this driver
};
// the structure returned to Zend that exposes the extension to the Zend engine.
// this structure is defined in zend_modules.h in the PHP sources
zend_module_entry g_pdo_sqlsrv_module_entry =
{
STANDARD_MODULE_HEADER_EX,
NULL,
pdo_sqlsrv_depends,
"pdo_sqlsrv",
pdo_sqlsrv_functions, // exported function table
// initialization and shutdown functions
PHP_MINIT(pdo_sqlsrv),
PHP_MSHUTDOWN(pdo_sqlsrv),
PHP_RINIT(pdo_sqlsrv),
PHP_RSHUTDOWN(pdo_sqlsrv),
PHP_MINFO(pdo_sqlsrv),
// version of the extension. Matches the version resource of the extension dll
VER_FILEVERSION_STR,
PHP_MODULE_GLOBALS(pdo_sqlsrv),
NULL,
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed
void pdo_error_dtor( _Inout_ zval* elem ) {
pdo_error* error_to_ignore = reinterpret_cast<pdo_error*>( Z_PTR_P( elem ) );
pefree( error_to_ignore, 1 );
}
// Module initialization
// This function is called once per execution of the Zend engine
PHP_MINIT_FUNCTION(pdo_sqlsrv)
{
// SQLSRV_UNUSED( type );
// our global variables are initialized in the RINIT function
#if defined(ZTS)
if( ts_allocate_id( &pdo_sqlsrv_globals_id,
sizeof( zend_pdo_sqlsrv_globals ),
(ts_allocate_ctor) NULL,
(ts_allocate_dtor) NULL ) == 0 )
return FAILURE;
ZEND_TSRMLS_CACHE_UPDATE();
#endif
core_sqlsrv_register_severity_checker(pdo_severity_check);
REGISTER_INI_ENTRIES();
PDO_LOG_NOTICE("pdo_sqlsrv: entering minit");
// initialize list of pdo errors
g_pdo_errors_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
::zend_hash_init( g_pdo_errors_ht, 50, NULL, pdo_error_dtor /*pDestructor*/, 1 );
for( int i = 0; PDO_ERRORS[i].error_code != -1; ++i ) {
void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[i].error_code,
&( PDO_ERRORS[i].sqlsrv_error ), sizeof( PDO_ERRORS[i].sqlsrv_error ) );
if( zr == NULL ) {
LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." );
return FAILURE;
}
}
try {
// register all attributes supported by this driver.
for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) {
REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value );
}
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT );
// retrieve the handles for the environments
core_sqlsrv_minit( &g_pdo_henv_cp, &g_pdo_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" );
}
catch( ... ) {
return FAILURE;
}
php_pdo_register_driver( &pdo_sqlsrv_driver );
return SUCCESS;
}
// Module shutdown function
// This function is called once per execution of the Zend engine
PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)
{
try {
// SQLSRV_UNUSED( type );
UNREGISTER_INI_ENTRIES();
php_pdo_unregister_driver( &pdo_sqlsrv_driver );
// clean up the list of pdo errors
zend_hash_destroy( g_pdo_errors_ht );
pefree( g_pdo_errors_ht, 1 /*persistent*/ );
core_sqlsrv_mshutdown( *g_pdo_henv_cp, *g_pdo_henv_ncp );
}
catch( ... ) {
PDO_LOG_NOTICE("Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)");
return FAILURE;
}
return SUCCESS;
}
// Request initialization function
// This function is called once per PHP script execution
PHP_RINIT_FUNCTION(pdo_sqlsrv)
{
// SQLSRV_UNUSED( module_number );
// SQLSRV_UNUSED( type );
#if defined(ZTS)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
#ifndef _WIN32
// if necessary, set locale from the environment for ODBC, which MUST be done before any connection
int set_locale = PDO_SQLSRV_G(set_locale_info);
if (set_locale == 2) {
setlocale(LC_ALL, "");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_ALL");
}
else if (set_locale == 1) {
setlocale(LC_CTYPE, "");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_CTYPE");
}
else {
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale NONE");
}
#endif
PDO_LOG_NOTICE("pdo_sqlsrv: entering rinit");
return SUCCESS;
}
// Request shutdown
// Called at the end of a script's execution
PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv)
{
// SQLSRV_UNUSED( module_number );
// SQLSRV_UNUSED( type );
PDO_LOG_NOTICE("pdo_sqlsrv: entering rshutdown");
return SUCCESS;
}
// Called for php_info();
// Displays the INI settings registered and their current values
PHP_MINFO_FUNCTION(pdo_sqlsrv)
{
php_info_print_table_start();
php_info_print_table_header(2, "pdo_sqlsrv support", "enabled");
php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR);
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
// *** internal init functions ***
namespace {
// mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because
// we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT)
void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( _In_z_ char const* name, _In_ long value )
{
zend_class_entry* zend_class = php_pdo_get_dbh_ce();
SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: php_pdo_get_dbh_ce failed");
zend_declare_class_constant_long(zend_class, const_cast<char*>(name), strnlen_s(name), value);
}
void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value )
{
zend_class_entry* zend_class = php_pdo_get_dbh_ce();
SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: php_pdo_get_dbh_ce failed");
zend_declare_class_constant_string(zend_class, const_cast<char*>(name), strnlen_s(name), const_cast<char*>(value));
}
// array of pdo constants.
sqlsrv_attr_pdo_constant pdo_attr_constants[] = {
// driver specific attributes
{ "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING },
{ "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT },
{ "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY },
{ "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE },
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
{ "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE },
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
{ "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS },
{ "SQLSRV_ATTR_DECIMAL_PLACES" , SQLSRV_ATTR_DECIMAL_PLACES },
{ "SQLSRV_ATTR_DATA_CLASSIFICATION" , SQLSRV_ATTR_DATA_CLASSIFICATION },
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
// PDO::PARAM_STR uses the size of the string in the variable
{ "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 },
// encoding attributes
{ "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT },
{ "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM },
{ "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY },
{ "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 },
// cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE
{ "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC },
{ "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC },
{ "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN },
{ "SQLSRV_CURSOR_BUFFERED" , static_cast<int>(SQLSRV_CURSOR_BUFFERED) },
{ NULL , 0 } // terminate the table
};
}
// DllMain for the extension.
#ifdef _WIN32
// Only needed if extension is built shared
#ifdef COMPILE_DL_PDO_SQLSRV
BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, LPVOID )
{
switch( fdwReason ) {
case DLL_PROCESS_ATTACH:
// store the module handle for use by client_info and server_info
g_sqlsrv_hmodule = hinstDLL;
break;
default:
break;
}
return TRUE;
}
#endif
#endif

View file

@ -1,450 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_parser.cpp
//
// Contents: Implements a parser to parse the PDO DSN.
//
// Copyright Microsoft Corporation
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
// Constructor
conn_string_parser:: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht )
{
this->orig_str = dsn;
this->len = len;
this->element_ht = conn_options_ht;
this->pos = -1;
this->ctx = &ctx;
this->current_key = 0;
this->current_key_name = NULL;
}
sql_string_parser:: sql_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholders_ht )
{
this->orig_str = sql_str;
this->len = len;
this->element_ht = placeholders_ht;
this->pos = -1;
this->ctx = &ctx;
this->current_key = 0;
}
// Move to the next character
inline bool string_parser::next( void )
{
// if already at the end then return false
if( this->is_eos() ) {
return false;
}
SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" );
this->pos++;
if ( this->is_eos() ) {
return false;
}
return true;
}
// Check for end of string.
inline bool string_parser::is_eos( void )
{
if( this->pos == len )
{
return true; // EOS
}
SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" );
return false;
}
// Check for white space.
inline bool string_parser::is_white_space( _In_ char c )
{
if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) {
return true;
}
return false;
}
// Discard any trailing white spaces.
int conn_string_parser::discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len )
{
const char* end = str + ( len - 1 );
while(( this->is_white_space( *end ) ) && (len > 0) ) {
len--;
end--;
}
return len;
}
// Discard white spaces.
bool string_parser::discard_white_spaces()
{
if( this->is_eos() ) {
return false;
}
while( this->is_white_space( this->orig_str[pos] )) {
if( !next() )
return false;
}
return true;
}
// Add a key-value pair to the hashtable
void string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len )
{
zval value_z;
ZVAL_UNDEF( &value_z );
if( len == 0 ) {
ZVAL_STRINGL( &value_z, "", 0);
}
else {
ZVAL_STRINGL( &value_z, const_cast<char*>( value ), len );
}
core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z );
}
// Add a key-value pair to the hashtable with int value
void sql_string_parser::add_key_int_value_pair( _In_ unsigned int value ) {
zval value_z;
ZVAL_LONG( &value_z, value );
core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z );
}
// Validate a given DSN keyword.
void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len )
{
int new_len = discard_trailing_white_spaces( key, key_len );
for( int i=0; PDO_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i )
{
// discard the null terminator.
if( new_len == ( PDO_CONN_OPTS[i].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[i].sqlsrv_name, new_len )) {
this->current_key = PDO_CONN_OPTS[i].conn_option_key;
this->current_key_name = PDO_CONN_OPTS[i].sqlsrv_name;
return;
}
}
// encountered an invalid key, throw error.
sqlsrv_malloc_auto_ptr<char> key_name;
key_name = static_cast<char*>( sqlsrv_malloc( new_len + 1 ));
memcpy_s( key_name, new_len + 1 ,key, new_len );
key_name[new_len] = '\0';
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast<char*>( key_name ) );
}
inline bool sql_string_parser::is_placeholder_char( char c )
{
// placeholder only accepts numbers, upper and lower case alphabets and underscore
if (( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || c == '_' ) {
return true;
}
return false;
}
// Primary function which parses the connection string/DSN.
void conn_string_parser:: parse_conn_string( void )
{
States state = FirstKeyValuePair; // starting state
int start_pos = -1;
try {
while( !this->is_eos() ) {
switch( state ) {
case FirstKeyValuePair:
{
// discard leading spaces
if( !next() || !discard_white_spaces() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS
}
state = Key;
break;
}
case Key:
{
start_pos = this->pos;
// read the key name
while( this->orig_str[pos] != '=' ) {
if( !next() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS
}
}
this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) );
state = Value;
break;
}
case Value:
{
SQLSRV_ASSERT(( this->orig_str[pos] == '=' ), "conn_string_parser:: parse_conn_string: "
"Equal was expected" );
next(); // skip "="
// if EOS encountered after 0 or more spaces OR semi-colon encountered.
if( !discard_white_spaces() || this->orig_str[pos] == ';' ) {
add_key_value_pair( NULL, 0 );
if( this->is_eos() ) {
break; // EOS
}
else {
// this->orig_str[pos] == ';'
state = NextKeyValuePair;
}
}
// if LCB
else if( this->orig_str[pos] == '{' ) {
start_pos = this->pos; // starting character is LCB
state = ValueContent1;
}
// If NonSP-LCB-SC
else {
start_pos = this->pos;
state = ValueContent2;
}
break;
}
case ValueContent1:
{
while ( this->orig_str[pos] != '}' ) {
if ( ! next() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name );
}
}
// If we reached here than RCB encountered
state = RCBEncountered;
break;
}
case ValueContent2:
{
while( this->orig_str[pos] != ';' ) {
if( ! next() ) {
break; //EOS
}
}
if( !this->is_eos() && this->orig_str[pos] == ';' ) {
// semi-colon encountered, so go to next key-value pair
state = NextKeyValuePair;
}
add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos );
SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )),
"conn_string_parser::parse_conn_string: Invalid state encountered " );
break;
}
case RCBEncountered:
{
// Read the next character after RCB.
if( !next() ) {
// EOS
add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos );
break;
}
SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" );
// if second RCB encountered than go back to ValueContent1
if( this->orig_str[pos] == '}' ) {
if( !next() ) {
// EOS after a second RCB is error
THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name );
}
state = ValueContent1;
break;
}
int end_pos = this->pos;
// discard any trailing white-spaces.
if( this->is_white_space( this->orig_str[pos] )) {
if( ! this->discard_white_spaces() ) {
//EOS
add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos );
break;
}
}
// if semi-colon than go to next key-value pair
if ( this->orig_str[pos] == ';' ) {
add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos );
state = NextKeyValuePair;
break;
}
// Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error.
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name );
break;
}
case NextKeyValuePair:
{
SQLSRV_ASSERT(( this->orig_str[pos] == ';' ),
"conn_string_parser::parse_conn_string: semi-colon was expected." );
// Call next() to skip the semi-colon.
if( !next() || !this->discard_white_spaces() ) {
// EOS
break;
}
if( this->orig_str[pos] == ';' ) {
// a second semi-colon is error case.
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos );
}
else {
// any other character leads to the next key
state = Key;
break;
}
} //case NextKeyValuePair
} // switch
} //while
}
catch( pdo::PDOException& ) {
throw;
}
}
// Primary function which parses out the named placeholders from a sql string.
void sql_string_parser::parse_sql_string( void ) {
try {
int start_pos = -1;
while ( !this->is_eos() ) {
// if pos is -1, then reading from a string is an initialized read
if ( pos == -1 ) {
next();
}
// skip until a '"', '\'', ':' or '?'
char sym;
while ( this->orig_str[pos] != '"' && this->orig_str[pos] != '\'' && this->orig_str[pos] != ':' && this->orig_str[pos] != '?' && !this->is_eos() ) {
next();
}
sym = this->orig_str[pos];
// if '"' or '\'', skip until the next '"' or '\'' respectively
if ( sym == '"' || sym == '\'' ) {
next();
while ( this->orig_str[pos] != sym && !this->is_eos() ) {
next();
}
}
// if ':', store string placeholder in the placeholders hashtable
else if ( sym == ':' ) {
start_pos = this->pos;
next();
// keep going until the next space or line break
// while (!is_white_space(this->orig_str[pos]) && !this->is_eos()) {
while ( is_placeholder_char( this->orig_str[pos] )) {
next();
}
add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos );
discard_white_spaces();
// if an '=' is right after a placeholder, it means the placeholder is for output parameters
// and emulate prepare does not support output parameters
if (this->orig_str[pos] == '=') {
THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
}
this->current_key++;
}
// if '?', store long placeholder into the placeholders hashtable
else if ( sym == '?' ) {
next();
// add dummy value to placeholders ht to keep count of the number of placeholders
add_key_int_value_pair( this->current_key );
discard_white_spaces();
if (this->orig_str[pos] == '=') {
THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
}
this->current_key++;
}
}
}
catch ( pdo::PDOException& ) {
throw;
}
}

View file

@ -1,1572 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_stmt.cpp
//
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
// *** internal variables and constants ***
namespace {
// Maps to the list of PDO::FETCH_ORI_* constants
SQLSMALLINT odbc_fetch_orientation[] =
{
SQL_FETCH_NEXT, // PDO_FETCH_ORI_NEXT
SQL_FETCH_PRIOR, // PDO_FETCH_ORI_PRIOR
SQL_FETCH_FIRST, // PDO_FETCH_ORI_FIRST
SQL_FETCH_LAST, // PDO_FETCH_ORI_LAST
SQL_FETCH_ABSOLUTE, // PDO_FETCH_ORI_ABS
SQL_FETCH_RELATIVE // PDO_FETCH_ORI_REL
};
// max length of a field type
const int SQL_SERVER_IDENT_SIZE_MAX = 128;
inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori ( _In_ enum pdo_fetch_orientation ori )
{
SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range.");
#ifdef _WIN32
OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" );
#endif
return odbc_fetch_orientation[ori];
}
// Returns SQLSRV data type for a given PDO type. See pdo_param_type
// for list of supported pdo types.
SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>(driver_stmt);
SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null");
switch( pdo_type ) {
case PDO_PARAM_BOOL:
case PDO_PARAM_INT:
return SQLSRV_PHPTYPE_INT;
case PDO_PARAM_STR:
return SQLSRV_PHPTYPE_STRING;
case PDO_PARAM_NULL:
return SQLSRV_PHPTYPE_NULL;
case PDO_PARAM_LOB:
if (pdo_stmt->fetch_datetime) {
return SQLSRV_PHPTYPE_DATETIME;
} else {
// TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented.
return SQLSRV_PHPTYPE_STRING;
}
case PDO_PARAM_STMT:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
break;
default:
DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" );
}
return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning
}
// Returns a pdo type for a given SQL type. See pdo_param_type
// for list of supported pdo types.
inline pdo_param_type sql_type_to_pdo_type( _In_ SQLSMALLINT sql_type )
{
pdo_param_type return_type = PDO_PARAM_STR;
switch( sql_type ) {
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_BIGINT:
case SQL_BINARY:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_DOUBLE:
case SQL_FLOAT:
case SQL_GUID:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_NUMERIC:
case SQL_REAL:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_UDT:
case SQL_SS_VARIANT:
case SQL_SS_XML:
case SQL_TYPE_DATE:
case SQL_TYPE_TIMESTAMP:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WCHAR:
case SQL_WLONGVARCHAR:
case SQL_WVARCHAR:
return_type = PDO_PARAM_STR;
break;
default: {
DIE( "sql_type_to_pdo_type: Invalid SQL type provided." );
break;
}
}
return return_type;
}
// Calls core_sqlsrv_set_scrollable function to set cursor.
// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL.
void set_stmt_cursors( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
}
zend_long pdo_cursor_type = Z_LVAL_P( value_z );
long odbc_cursor_type = -1;
switch( pdo_cursor_type ) {
case PDO_CURSOR_FWDONLY:
odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY;
break;
case PDO_CURSOR_SCROLL:
odbc_cursor_type = SQL_CURSOR_STATIC;
break;
default:
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
}
core_sqlsrv_set_scrollable( stmt, odbc_cursor_type );
}
void set_stmt_cursor_scroll_type( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE );
}
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE );
}
long odbc_cursor_type = static_cast<long>( Z_LVAL_P( value_z ) );
core_sqlsrv_set_scrollable( stmt, odbc_cursor_type );
return;
}
// Sets the statement encoding. Default encoding on the statement
// implies use the connection's encoding.
void set_stmt_encoding( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
// validate the value
if( Z_TYPE_P( value_z ) != IS_LONG ) {
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
}
zend_long attr_value = Z_LVAL_P( value_z );
switch( attr_value ) {
// when the default encoding is applied to a statement, it means use the creating connection's encoding
case SQLSRV_ENCODING_DEFAULT:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_UTF8:
stmt->set_encoding( static_cast<SQLSRV_ENCODING>( attr_value ));
break;
default:
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
break;
}
}
zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len )
{
zval out_zval;
ZVAL_UNDEF(&out_zval);
switch (sqlsrv_php_type) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
if (*in_val == NULL) {
ZVAL_NULL(&out_zval);
}
else {
if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) {
ZVAL_LONG(&out_zval, **(reinterpret_cast<int**>(in_val)));
}
else {
ZVAL_DOUBLE(&out_zval, **(reinterpret_cast<double**>(in_val)));
}
}
if (*in_val) {
sqlsrv_free(*in_val);
}
break;
}
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented
{
if (*in_val == NULL) {
ZVAL_NULL(&out_zval);
}
else {
ZVAL_STRINGL(&out_zval, reinterpret_cast<char*>(*in_val), field_len);
sqlsrv_free(*in_val);
}
break;
}
case SQLSRV_PHPTYPE_DATETIME:
convert_datetime_string_to_zval(stmt, static_cast<char*>(*in_val), field_len, out_zval);
sqlsrv_free(*in_val);
break;
case SQLSRV_PHPTYPE_NULL:
ZVAL_NULL(&out_zval);
break;
default:
DIE("Unknown php type");
break;
}
return out_zval;
}
} // namespace
int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori,
_In_ zend_long offset );
int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
_Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type );
int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno );
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
_Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees );
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result, _Inout_ enum pdo_param_type *type);
#endif
int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val );
int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt );
struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = {
pdo_sqlsrv_stmt_dtor,
pdo_sqlsrv_stmt_execute,
pdo_sqlsrv_stmt_fetch,
pdo_sqlsrv_stmt_describe_col,
pdo_sqlsrv_stmt_get_col_data,
pdo_sqlsrv_stmt_param_hook,
pdo_sqlsrv_stmt_set_attr,
pdo_sqlsrv_stmt_get_attr,
pdo_sqlsrv_stmt_get_col_meta,
pdo_sqlsrv_stmt_next_rowset,
pdo_sqlsrv_stmt_close_cursor
};
void stmt_option_pdo_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
set_stmt_cursors( stmt, value_z );
}
void stmt_option_encoding:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
set_stmt_encoding( stmt, value_z );
}
void stmt_option_direct_query:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->direct_query = zend_is_true(value_z);
}
void stmt_option_cursor_scroll_type:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
set_stmt_cursor_scroll_type( stmt, value_z );
}
void stmt_option_emulate_prepares:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_stmt_t *pdo_stmt = static_cast<pdo_stmt_t*>( stmt->driver() );
pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL;
}
void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_numeric = zend_is_true(value_z);
}
void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_datetime = zend_is_true(value_z);
}
// log a function entry point
#define PDO_LOG_STMT_ENTRY \
{ \
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data ); \
if (driver_stmt != NULL) driver_stmt->set_func( __FUNCTION__ ); \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}
// PDO SQLSRV statement destructor
pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void )
{
if( bound_column_param_types ) {
sqlsrv_free( bound_column_param_types );
bound_column_param_types = NULL;
}
if( direct_query_subst_string ) {
// we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme
// and we use estrdup to allocate this string, which uses emalloc
efree( reinterpret_cast<void*>( const_cast<char*>( direct_query_subst_string )));
}
}
// pdo_sqlsrv_stmt_close_cursor
// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor.
// Parameters:
// *stmt - Pointer to current statement
// Return:
// Returns 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: pdo_stmt object was null" );
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: driver_data object was null" );
// to "close the cursor" means we make the statement ready for execution again. To do this, we
// skip all the result sets on the current statement.
// If the statement has not been executed there are no next results to iterate over.
if ( driver_stmt && driver_stmt->executed == true )
{
while( driver_stmt && driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt );
}
}
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_close_cursor: Unknown exception occurred while advancing to the next result set." );
}
return 1;
}
// pdo_sqlsrv_stmt_describe_col
// Gets the metadata for a column based on the column number.
// Calls the core_sqlsrv_field_metadata function present in the core layer.
// Parameters:
// *stmt - pointer to current statement
// colno - Index of the column which requires description.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." );
SQLSRV_ASSERT( stmt->driver_data != NULL, "pdo_sqlsrv_stmt_describe_col: driver_data object was NULL." );
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
try {
core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data ), colno );
}
catch( core::CoreException& ) {
return 0;
}
catch(...) {
DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." );
}
pdo_column_data* column_data = &(stmt->columns[colno]);
SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" );
// Set the name
column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 );
// Set the maxlen
column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size;
// Set the precision
column_data->precision = core_meta_data->field_scale;
#if PHP_VERSION_ID < 80100
// Set the param_type
column_data->param_type = PDO_PARAM_ZVAL;
#endif
// store the field data for use by pdo_sqlsrv_stmt_get_col_data
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" );
driver_stmt->current_meta_data.push_back( core_meta_data.get() );
SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" );
core_meta_data.transferred();
return 1;
}
// pdo_sqlsrv_stmt_dtor
// Maps to PDOStatement::__destruct. Destructor for the PDO Statement.
// Parameters:
// *stmt - pointer to current statement
// Return:
// 1 for success.
int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt )
{
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" );
// if a PDO statement didn't complete preparation, its driver_data can be NULL
if (driver_stmt == NULL) {
return 1;
}
// occasionally stmt->dbh->driver_data is already freed and reset but its driver_data is not
if (stmt->dbh != NULL && stmt->dbh->driver_data == NULL) {
stmt->driver_data = NULL;
return 1;
}
if ( driver_stmt->placeholders != NULL ) {
zend_hash_destroy( driver_stmt->placeholders );
FREE_HASHTABLE( driver_stmt->placeholders );
driver_stmt->placeholders = NULL;
}
(( sqlsrv_stmt* )driver_stmt )->~sqlsrv_stmt();
sqlsrv_free( driver_stmt );
stmt->driver_data = NULL;
return 1;
}
// pdo_sqlsrv_stmt_execute
// Maps to PDOStatement::Execute. Executes the prepared statement.
// Parameters:
// *stmt - pointer to the current statement.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" );
// prepare for execution by flushing anything remaining in the result set if it wasn't already
// done before binding parameters
if( driver_stmt && driver_stmt->executed && !driver_stmt->past_next_result_end ) {
while( driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt, false );
}
}
const char* query = NULL;
unsigned int query_len = 0;
// if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here
if( driver_stmt->direct_query ) {
query = driver_stmt->direct_query_subst_string;
query_len = static_cast<unsigned int>( driver_stmt->direct_query_subst_string_len );
}
// if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the
// subtituted query provided by PDO
if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
// reset the placeholders hashtable internal in case the user reexecutes a statement
// Normally it's not a good idea to alter the internal pointer in a hashed array
// (see pull request 634 on GitHub) but in this case this is for internal use only
zend_hash_internal_pointer_reset(driver_stmt->placeholders);
#if PHP_VERSION_ID < 80100
query = stmt->active_query_string;
query_len = static_cast<unsigned int>(stmt->active_query_stringlen);
#else
query = ZSTR_VAL(stmt->active_query_string);
query_len = ZSTR_LEN(stmt->active_query_string);
#endif
}
// The query timeout setting is inherited from the corresponding connection attribute, but
// the user may have changed the query timeout setting again before this via
// PDOStatement::setAttribute()
driver_stmt->set_query_timeout();
SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, query, query_len );
if ( execReturn == SQL_NO_DATA ) {
stmt->column_count = 0;
stmt->row_count = 0;
driver_stmt->column_count = 0;
driver_stmt->row_count = 0;
}
else {
if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
stmt->column_count = core::SQLNumResultCols( driver_stmt );
driver_stmt->column_count = stmt->column_count;
}
else {
stmt->column_count = driver_stmt->column_count;
}
if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) {
// return the row count regardless if there are any rows or not
stmt->row_count = core::SQLRowCount( driver_stmt );
driver_stmt->row_count = stmt->row_count;
}
else {
stmt->row_count = driver_stmt->row_count;
}
}
// workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with
// the following sequence:
// 1) Prepare and execute a statement (that has some results with it)
// 2) call PDOStatement::nextRowset until there are no more results
// 3) execute the statement again
// 4) call PDOStatement::getColumnMeta
// It crashes from what I can tell because there is no metadata because there was no call to
// pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to
// PDO::execute. My guess is that because stmt->executed is true, it optimizes away a necessary call to
// pdo_sqlsrv_stmt_describe_col. By setting the stmt->executed flag to 0, this call is not optimized away
// and the crash disappears.
if( stmt->columns == NULL ) {
stmt->executed = 0;
}
}
catch( core::CoreException& /*e*/ ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." );
}
// success
return 1;
}
// pdo_sqlsrv_stmt_fetch
// Maps to PDOStatement::fetch
// Move the cursor to the record indicated. If the cursor is moved off the end,
// or before the beginning if a scrollable cursor is created, then FAILURE is returned.
// Parameters:
// *stmt - pointer to current statement for which the cursor should be moved.
// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants
// offset - For orientations that use it, offset to move to.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori,
_In_ zend_long offset)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" );
// set the types for bound columns to zval so that PDO does no conversion when the value
// is returned by pdo_sqlsrv_get_col_data. Remember the types that were bound by the user
// and use it to manually convert data types
if( stmt->bound_columns ) {
pdo_bound_param_data* bind_data = NULL;
if( !driver_stmt->bound_column_param_types ) {
#if PHP_VERSION_ID < 80100
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 ));
std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
PDO_PARAM_ZVAL );
#else
// TODO: possibly no longer need bound_column_param_types?? default to PDO_PARAM_STR???
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>(sqlsrv_malloc(stmt->column_count, sizeof(pdo_param_type), 0));
std::fill(driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
PDO_PARAM_STR);
#endif
}
for( long i = 0; i < stmt->column_count; ++i ) {
#if PHP_VERSION_ID < 80100
if (NULL== (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
(NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
driver_stmt->bound_column_param_types[i] = PDO_PARAM_ZVAL;
continue;
}
if( bind_data->param_type != PDO_PARAM_ZVAL ) {
driver_stmt->bound_column_param_types[i] = bind_data->param_type;
bind_data->param_type = PDO_PARAM_ZVAL;
}
#else
if (NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
(NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
continue;
}
// TODO: possibly no longer need bound_column_param_types??
driver_stmt->bound_column_param_types[i] = bind_data->param_type;
#endif
}
}
SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori );
bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset );
// support for the PDO rowCount method. Since rowCount doesn't call a
// method, PDO relies on us to fill the pdo_stmt_t::row_count member
// The if condition was changed from
// `driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY`
// because it caused SQLRowCount to be called at each fetch if using a non-forward cursor
// which is unnecessary and a performance hit
if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) {
stmt->row_count = core::SQLRowCount( driver_stmt );
driver_stmt->row_count = stmt->row_count;
// a row_count of -1 means no rows, but we change it to 0
if( stmt->row_count == -1 ) {
stmt->row_count = 0;
}
}
// if no data was returned, then return false so data isn't retrieved
if( !data ) {
return 0;
}
return 1;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
return 0;
}
// pdo_sqlsrv_stmt_get_col_data
// Called by the set of PDO Fetch functions.
// Retrieves a single column. PDO driver manager is responsible for freeing the
// returned buffer. Because PDO can request fields out of order and ODBC does not
// support out of order field requests, this function should also cache fields.
// Parameters:
// stmt - Statement to retrive the column for.
// colno - Index of the column that needs to be retrieved. Starts with 0.
// ptr - Returns the buffer containing the column data.
// len - Length of the buffer returned.
// caller_frees - Flag to let the PDO driver manager know that it is responsible for
// freeing the memory.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
_Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees)
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result_z, _Inout_ enum pdo_param_type *type)
#endif
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null");
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null");
CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX) {
return 0;
}
#if PHP_VERSION_ID < 80100
// Let PDO free the memory after use.
* caller_frees = 1;
#endif
// translate the pdo type to a type the core layer understands
sqlsrv_phptype sqlsrv_php_type;
SQLSRV_ASSERT(colno >= 0 && colno < static_cast<int>(driver_stmt->current_meta_data.size()),
"Invalid column number in pdo_sqlsrv_stmt_get_col_data");
// set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
// save the php type for next use
sqlsrv_php_type = driver_stmt->sql_type_to_php_type(
static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
true);
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
// if a column is bound to a type different than the column type, figure out a way to convert it to the
// type they want
#if PHP_VERSION_ID < 80100
if (stmt->bound_columns && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_ZVAL) {
#else
if (stmt->bound_columns) {
#endif
sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type(driver_stmt,
driver_stmt->bound_column_param_types[colno]
);
pdo_bound_param_data* bind_data = NULL;
bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, colno));
if (bind_data == NULL) {
// can't find by index then try searching by name
bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[colno].name));
}
if (bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params)) {
CHECK_CUSTOM_ERROR(Z_TYPE(bind_data->driver_params) != IS_LONG, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1) {
throw pdo::PDOException();
}
CHECK_CUSTOM_ERROR(driver_stmt->bound_column_param_types[colno] != PDO_PARAM_STR
&& driver_stmt->bound_column_param_types[colno] != PDO_PARAM_LOB, driver_stmt,
PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1) {
throw pdo::PDOException();
}
sqlsrv_php_type.typeinfo.encoding = Z_LVAL(bind_data->driver_params);
switch (sqlsrv_php_type.typeinfo.encoding) {
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_UTF8:
break;
default:
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno);
break;
}
}
// save the php type for the bound column
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
}
SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;
#if PHP_VERSION_ID < 80100
core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast<void**>(ptr)),
reinterpret_cast<SQLLEN*>(len), true, &sqlsrv_phptype_out);
if (ptr) {
zval* zval_ptr = reinterpret_cast<zval*>(sqlsrv_malloc(sizeof(zval)));
*zval_ptr = convert_to_zval(driver_stmt, sqlsrv_phptype_out, reinterpret_cast<void**>(ptr), *len);
*ptr = reinterpret_cast<char*>(zval_ptr);
*len = sizeof(zval);
}
#else
SQLLEN len = 0;
void *ptr = NULL;
core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, ptr, &len, true, &sqlsrv_phptype_out);
if (ptr) {
*result_z = convert_to_zval(driver_stmt, sqlsrv_phptype_out, &ptr, len);
}
#endif
return 1;
}
catch ( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
return 0;
}
// pdo_sqlsrv_stmt_set_attr
// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement.
// Parameters:
// stmt - Current statement on which the attribute should be set.
// attr - Represents any valid set of attribute constants supported by this driver.
// val - Attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_set_attr: driver_data object was null" );
try {
switch( attr ) {
case SQLSRV_ATTR_DIRECT_QUERY:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY );
break;
case SQLSRV_ATTR_ENCODING:
set_stmt_encoding( driver_stmt, val );
break;
case PDO_ATTR_CURSOR:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY );
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
core_sqlsrv_set_query_timeout( driver_stmt, val );
break;
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY );
break;
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
core_sqlsrv_set_buffered_query_limit( driver_stmt, val );
break;
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
driver_stmt->fetch_numeric = zend_is_true(val);
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_stmt->fetch_datetime = zend_is_true(val);
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
driver_stmt->format_decimals = zend_is_true(val);
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
core_sqlsrv_set_decimal_places(driver_stmt, val);
break;
case SQLSRV_ATTR_DATA_CLASSIFICATION:
driver_stmt->data_classification = zend_is_true(val);
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
}
}
catch( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred.");
}
return 1;
}
// pdo_sqlsrv_stmt_get_attr
// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement.
// Parameters:
// stmt - Current statement for which the attribute value is requested.
// attr - Represents any valid set of attribute constants supported by this driver.
// return_value - Attribute value.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" );
try {
switch( attr ) {
case SQLSRV_ATTR_DIRECT_QUERY:
{
ZVAL_BOOL( return_value, driver_stmt->direct_query );
break;
}
case SQLSRV_ATTR_ENCODING:
{
ZVAL_LONG( return_value, driver_stmt->encoding() );
break;
}
case PDO_ATTR_CURSOR:
{
ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ?
PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY ));
break;
}
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
{
ZVAL_LONG( return_value, driver_stmt->cursor_type );
break;
}
case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE:
{
ZVAL_LONG( return_value, driver_stmt->buffered_query_limit );
break;
}
case SQLSRV_ATTR_QUERY_TIMEOUT:
{
ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout ));
break;
}
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
{
ZVAL_BOOL( return_value, driver_stmt->fetch_numeric );
break;
}
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
{
ZVAL_BOOL( return_value, driver_stmt->fetch_datetime );
break;
}
case SQLSRV_ATTR_FORMAT_DECIMALS:
{
ZVAL_BOOL( return_value, driver_stmt->format_decimals );
break;
}
case SQLSRV_ATTR_DECIMAL_PLACES:
{
ZVAL_LONG( return_value, driver_stmt->decimal_places );
break;
}
case SQLSRV_ATTR_DATA_CLASSIFICATION:
{
ZVAL_BOOL(return_value, driver_stmt->data_classification);
break;
}
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
}
}
catch( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred.");
}
return 1;
}
// pdo_sqlsrv_stmt_get_col_meta
// Maps to PDOStatement::getColumnMeta. Return extra metadata.
// Though we don't return any extra metadata, PDO relies on us to
// create the associative array that holds the standard information,
// so we create one and return it for PDO's use.
// Parameters:
// stmt - Current statement.
// colno - The index of the field for which to return the metadata.
// return_value - zval* consisting of the metadata.
// Return:
// FAILURE for failure, SUCCESS for success.
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" );
SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." );
sqlsrv_stmt* driver_stmt = static_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: stmt->driver_data was null");
// Based on PDOStatement::getColumnMeta API, this should return FALSE
// if the requested column does not exist in the result set, or if
// no result set exists. Thus, do not use SQLSRV_ASSERT, which causes
// the script to fail right away. Instead, log this warning if logging
// is enabled
if (colno < 0 || colno >= stmt->column_count || stmt->columns == NULL) {
LOG( SEV_WARNING, "Invalid column number %1!d!", colno );
return FAILURE;
}
// initialize the array to nothing, as PDO requires us to create it
array_init(return_value);
field_meta_data* core_meta_data;
// metadata should have been saved earlier
SQLSRV_ASSERT(colno < driver_stmt->current_meta_data.size(), "pdo_sqlsrv_stmt_get_col_meta: Metadata vector out of sync with column numbers");
core_meta_data = driver_stmt->current_meta_data[colno];
// add the following fields: flags, native_type, driver:decl_type, table
if (driver_stmt->data_classification) {
core_sqlsrv_sensitivity_metadata(driver_stmt);
// initialize the column data classification array
zval data_classification;
ZVAL_UNDEF(&data_classification);
array_init(&data_classification);
data_classification::fill_column_sensitivity_array(driver_stmt, (SQLSMALLINT)colno, &data_classification);
add_assoc_zval(return_value, "flags", &data_classification);
}
else {
add_assoc_long(return_value, "flags", 0);
}
// get the name of the data type
char field_type_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'};
SQLSMALLINT out_buff_len;
SQLLEN not_used;
core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name,
sizeof( field_type_name ), &out_buff_len, &not_used );
add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name );
// get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when
// given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only
// say string, since the length is given in another field of the metadata array.
long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type );
switch( pdo_type ) {
case PDO_PARAM_STR:
{
//Declarations eliminate compiler warnings about string constant to char* conversions
std::string key = "native_type";
std::string str = "string";
add_assoc_string( return_value, &key[0], &str[0] );
}
break;
default:
DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" );
break;
}
// add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec
char table_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'};
SQLLEN field_type_num;
core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX,
&out_buff_len, &field_type_num );
add_assoc_string( return_value, "table", table_name );
#if PHP_VERSION_ID < 80100
if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) {
add_assoc_long( return_value, "pdo_type", pdo_type );
}
#else
if (stmt->columns) {
add_assoc_long(return_value, "pdo_type", pdo_type);
}
#endif
}
catch( core::CoreException& ) {
zval_ptr_dtor(return_value);
return FAILURE;
}
catch(...) {
zval_ptr_dtor(return_value);
DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." );
}
return SUCCESS;
}
// pdo_sqlsrv_stmt_next_rowset
// Maps to PDOStatement::nextRowset.
// Move the cursor to the beginning of the next rowset in a multi-rowset result.
// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data.
// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data.
// If another result set is available, this function returns 1. Otherwise it returns 0.
// Parameters:
// stmt - PDOStatement object containing the result set.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" );
core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) );
// clear the current meta data since the new result will generate new meta data
driver_stmt->clean_up_results_metadata();
// if there are no more result sets, return that it failed.
if( driver_stmt->past_next_result_end == true ) {
return 0;
}
stmt->column_count = core::SQLNumResultCols( driver_stmt );
// return the row count regardless if there are any rows or not
stmt->row_count = core::SQLRowCount( driver_stmt );
driver_stmt->column_count = stmt->column_count;
driver_stmt->row_count = stmt->row_count;
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." );
}
return 1;
}
// pdo_sqlsrv_stmt_param_hook
// Maps to PDOStatement::bindColumn.
// Called by PDO driver manager to bind a parameter or column.
// This function pulls several duties for binding parameters and columns.
// It takes an event as a parameter that explains what the function should do on
// behalf of a parameter or column. We only use one of these events,
// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return.
// Paramters:
// stmt - PDO Statement object to bind a parameter.
// param - paramter to bind.
// event_type - Event to bind a parameter
// Return:
// Returns 0 for failure, 1 for success.
int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
_Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type)
{
PDO_RESET_STMT_ERROR;
try {
switch( event_type ) {
// since the param isn't reliable, we don't do anything here
case PDO_PARAM_EVT_ALLOC:
{
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
if (driver_stmt->conn->ce_option.enabled) {
if (driver_stmt->direct_query) {
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED);
}
if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
}
}
if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && (param->param_type & PDO_PARAM_INPUT_OUTPUT)) {
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
}
}
break;
case PDO_PARAM_EVT_FREE:
break;
// bind the parameter in the core layer
case PDO_PARAM_EVT_EXEC_PRE:
{
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
// skip column bindings
if( !param->is_param ) {
break;
}
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" );
// prepare for binding parameters by flushing anything remaining in the result set
if( driver_stmt->executed && !driver_stmt->past_next_result_end ) {
while( driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt, false );
}
}
int direction = SQL_PARAM_INPUT;
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
SQLSMALLINT decimal_digits = 0;
// determine the direction of the parameter. By default it's input, but if the user specifies a size
// that means they want output, and if they include the flag, then it's input/output.
// It's invalid to specify the input/output flag but not specify a length
CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0),
driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
throw pdo::PDOException();
}
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
// vso 2829: derive the pdo_type for input/output parameter as well
// also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params
int pdo_type = param->param_type;
if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
direction = SQL_PARAM_INPUT_OUTPUT;
pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
}
else {
direction = SQL_PARAM_OUTPUT;
}
}
// check if the user has specified the character set to use, take it off but ignore
#if PHP_VERSION_ID >= 70200
if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
pdo_type = pdo_type & ~PDO_PARAM_STR_NATL;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored.");
}
if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored.");
}
#endif
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
switch (pdo_type) {
case PDO_PARAM_BOOL:
case PDO_PARAM_INT:
php_out_type = SQLSRV_PHPTYPE_INT;
break;
case PDO_PARAM_STR:
php_out_type = SQLSRV_PHPTYPE_STRING;
break;
// when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is
// since the core layer keys off the zval type, we substitute a null for what they gave us
case PDO_PARAM_NULL:
{
zval null_zval;
php_out_type = SQLSRV_PHPTYPE_NULL;
ZVAL_NULL( &null_zval );
zval_ptr_dtor( &param->parameter );
param->parameter = null_zval;
break;
}
case PDO_PARAM_LOB:
php_out_type = SQLSRV_PHPTYPE_STREAM;
break;
case PDO_PARAM_STMT:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
break;
default:
SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." );
break;
}
// set the column size parameter for bind_param if we are expecting something back
if( direction != SQL_PARAM_INPUT ) {
switch( php_out_type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_STREAM:
{
zval *zv = &param->parameter;
if (Z_ISREF_P(zv)) {
ZVAL_DEREF(zv);
}
// Table-valued parameters are input-only
CHECK_CUSTOM_ERROR(Z_TYPE_P(zv) == IS_ARRAY, driver_stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
throw pdo::PDOException();
}
// For other types, simply throw the following error
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
break;
}
case SQLSRV_PHPTYPE_INT:
column_size = SQLSRV_UNKNOWN_SIZE;
break;
case SQLSRV_PHPTYPE_STRING:
{
CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) {
throw pdo::PDOException();
}
column_size = param->max_value_len;
break;
}
default:
SQLSRV_ASSERT( false, "Invalid PHP type for output parameter. Should have been caught already." );
break;
}
}
// block all objects from being bound as input or input/output parameters since there is a
// weird case:
// $obj = date_create();
// $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR
// that succeeds since the core layer implements DateTime object handling for the sqlsrv
// 2.0 driver. To be consistent and avoid surprises of one object type working and others
// not, we block all objects here.
CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT,
driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
throw pdo::PDOException();
}
// the encoding by default is that set on the statement
SQLSRV_ENCODING encoding = driver_stmt->encoding();
// if the statement's encoding is the default, then use the one on the connection
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = driver_stmt->conn->encoding();
}
// Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL
// But this extended type will be ignored in real prepared statements, so the encoding deliberately
// set in the statement or driver options will still take precedence
if( !Z_ISUNDEF(param->driver_params) ) {
CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
throw pdo::PDOException();
}
CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) {
throw pdo::PDOException();
}
encoding = static_cast<SQLSRV_ENCODING>( Z_LVAL( param->driver_params ));
switch( encoding ) {
case SQLSRV_ENCODING_SYSTEM:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_UTF8:
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
param->paramno + 1 );
break;
}
}
// and bind the parameter
core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
sql_type, column_size, decimal_digits);
}
break;
// undo any work done by the core layer after the statement is executed
case PDO_PARAM_EVT_EXEC_POST:
{
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
}
break;
case PDO_PARAM_EVT_FETCH_PRE:
break;
case PDO_PARAM_EVT_FETCH_POST:
break;
case PDO_PARAM_EVT_NORMALIZE:
break;
default:
DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" );
break;
}
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" );
}
return 1;
}
// Returns a sqlsrv_phptype for a given SQL Server data type.
sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_over_stream )
{
sqlsrv_phptype sqlsrv_phptype;
int local_encoding = this->encoding();
// if the encoding on the connection changed
if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) {
local_encoding = conn->encoding();
SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID,
"Invalid encoding on the connection. Must not be invalid or default." );
}
sqlsrv_phptype.typeinfo.encoding = local_encoding;
switch( sql_type ) {
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
if ( this->fetch_numeric ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
}
break;
case SQL_FLOAT:
case SQL_REAL:
if ( this->fetch_numeric ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
}
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
if ( this->fetch_datetime ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
}
break;
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_NUMERIC:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
break;
case SQL_CHAR:
case SQL_GUID:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
case SQL_SS_VARIANT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
break;
default:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
break;
}
return sqlsrv_phptype;
}

View file

@ -1,705 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_util.cpp
//
// Contents: Utility functions used by both connection or statement functions
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
#include "zend_exceptions.h"
// *** internal constants ***
namespace {
const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nError Message: %3!s!\n";
const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s";
char EXCEPTION_PROPERTY_MSG[] = "message";
char EXCEPTION_PROPERTY_CODE[] = "code";
char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo";
const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative
// the warning message is not the error message alone; it must take WARNING_TEMPLATE above into consideration without the formats
const int WARNING_MIN_LENGTH = static_cast<const int>( strnlen_s( WARNING_TEMPLATE ) - strnlen_s( "%1!s!%2!d!%3!s!" ));
// Returns a sqlsrv_error for a given error code.
sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code);
// build the object and throw the PDO exception
void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error);
void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args);
void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z);
}
// pdo driver error messages
// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0
pdo_error PDO_ERRORS[] = {
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver for SQL Server "
"for %1!s!: "
"https://go.microsoft.com/fwlink/?LinkId=163712", -1, true }
},
{
SQLSRV_ERROR_ZEND_HASH,
{ IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false }
},
{
PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
{ IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams "
"cannot be specified as output parameters.", -3, false }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point "
"numbers, strings, and streams may be used as parameters.", -4, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
{ IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true }
},
{
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true }
},
{
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true }
},
{
SQLSRV_ERROR_ZEND_STREAM,
{ IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false }
},
{
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true }
},
{
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
{ IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false }
},
{
SQLSRV_ERROR_FETCH_PAST_END,
{ IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, "
"no more data may be retrieved.", -13, false }
},
{
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
{ IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false }
},
{
SQLSRV_ERROR_NO_FIELDS,
{ IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false }
},
{
SQLSRV_ERROR_FETCH_NOT_CALLED,
{ IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch "
"functions was called.", -16, false }
},
{
SQLSRV_ERROR_NO_DATA,
{ IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true }
},
{
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true }
},
{
SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED,
{ IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false }
},
{
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false }
},
{
SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be"
" escaped with another right brace (}}).", -21, false }
},
{
SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN,
{ IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces "
"must be escaped with another right brace (}}).", -22, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true }
},
{
SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
{ IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of "
"key/value pairs.", -25, false }
},
{
SQLSRV_ERROR_INVALID_CONNECTION_KEY,
{ IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false }
},
{
SQLSRV_ERROR_INVALID_TYPE,
{ IMSSP, (SQLCHAR*) "Invalid type.", -27, false }
},
{
PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX,
{IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false }
},
{
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_KEY,
{ IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true }
},
{
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
{ IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DBH_ATTR,
{ IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false }
},
{
PDO_SQLSRV_ERROR_INVALID_STMT_ATTR,
{ IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false }
},
{
PDO_SQLSRV_ERROR_INVALID_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM,
{ IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants "
"such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false }
},
{
PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false }
},
{
PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR,
{ IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false }
},
{
PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR,
{ IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false }
},
{
PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR,
{ IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DSN_STRING,
{IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false }
},
{
PDO_SQLSRV_ERROR_INVALID_DSN_KEY,
{ IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true }
},
{
PDO_SQLSRV_ERROR_INVALID_STMT_OPTION,
{ IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false }
},
{
PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE,
{ IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or "
"PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false }
},
{
PDO_SQLSRV_ERROR_PARAM_PARSE,
{ IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false }
},
{
PDO_SQLSRV_ERROR_LAST_INSERT_ID,
{ IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false }
},
{
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true }
},
{
PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA,
{ IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only "
"encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true }
},
{
PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING,
{ IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types "
"can take an encoding option.", -49, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING,
{ IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE,
{ IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an "
"encoding option.", -51, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
{ IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true }
},
{
PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY,
{ IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the "
"$driver_options array of PDO::prepare.", -53, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
{ IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true }
},
{
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
{ IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output "
"parameter %1!d!.", -55, true }
},
{
PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION,
{ IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.",
-56, true }
},
{
PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE,
{ IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an "
"explicit length.", -57, true }
},
{
PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED,
{ IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false }
},
{
/* The stream related errors are not currently used in PDO, but the core layer can throw the stream related
errors so having a mapping here */
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
{ IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using "
"streams.", -59, false}
},
{
SQLSRV_ERROR_STREAM_CREATE,
{ IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false }
},
{
SQLSRV_ERROR_MARS_OFF,
{ IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. "
"To make the connection available for other queries, either fetch all results or cancel or free the statement. "
"For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false }
},
{
SQLSRV_ERROR_FIELD_INDEX_ERROR,
{ IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its "
"index is less than the index of a field that has already been retrieved (%2!d!).", -62, true }
},
{
PDO_SQLSRV_ERROR_INVALID_DSN_VALUE,
{ IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true }
},
{
PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED,
{ IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false }
},
{
PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY,
{ IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false }
},
{
PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING,
{ IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .",
-66, true }
},
{
PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE,
{ IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.",
-67, true }
},
{
PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY,
{ IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of "
"PDO::prepare.", -68, false }
},
{
PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE,
{ IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to "
"PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false }
},
{
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
{ IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not "
"positive. Only positive numbers are valid for this attribute.", -70, false }
},
{
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true }
},
{
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Statement with emulate prepare on does not support output or input_output parameters.", -72, false }
},
{
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server (or above) for %1!s!.", -78, true }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -79, true }
},
{
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
{ IMSSP, (SQLCHAR*) "The specified ODBC Driver is not found.", -80, false }
},
{
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Parameterized statement with attribute PDO::SQLSRV_ATTR_DIRECT_QUERY is not supported in a Column Encryption enabled Connection.", -81, false }
},
{
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.", -82, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, 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}
},
{
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -90, false}
},
{
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
},
{
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
{ IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -94, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.", -95, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true}
},
{
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID,
{ IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false}
},
{
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -98, true }
},
{
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -99, true }
},
{
SQLSRV_ERROR_TVP_FETCH_METADATA,
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -100, true }
},
{
SQLSRV_ERROR_TVP_INVALID_INPUTS,
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -101, true }
},
{
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -102, true }
},
{
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -103, true }
},
{
SQLSRV_ERROR_TVP_STRING_KEYS,
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -104, true }
},
{
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -105, true }
},
{
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -106, false }
},
{ UINT_MAX, {} }
};
bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args )
{
SQLSRV_ASSERT((ctx != NULL), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null");
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>(ctx.driver());
SQLSRV_ASSERT((dbh != NULL), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null");
sqlsrv_error_auto_ptr error;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args);
// error_mode is valid because PDO API has already taken care of invalid ones
if (!warning && dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
pdo_sqlsrv_throw_exception(error);
}
ctx.set_last_error(error);
// we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count
// return error ignored = true for warnings.
return (warning ? true : false);
}
// pdo error handler for the dbh context.
bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args )
{
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>( ctx.driver());
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" );
sqlsrv_error_auto_ptr error;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args);
// error_mode is valid because PDO API has already taken care of invalid ones
if (!warning) {
if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
pdo_sqlsrv_throw_exception(error);
}
else if (dbh->error_mode == PDO_ERRMODE_WARNING) {
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));
core_sqlsrv_format_message(msg, static_cast<unsigned int>(msg_len), WARNING_TEMPLATE, error->sqlstate, error->native_code,
error->native_message);
php_error(E_WARNING, "%s", msg.get());
}
}
ctx.set_last_error(error);
// return error ignored = true for warnings.
return (warning ? true : false);
}
// PDO error handler for the statement context.
bool pdo_sqlsrv_handle_stmt_error(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args)
{
pdo_stmt_t* pdo_stmt = reinterpret_cast<pdo_stmt_t*>(ctx.driver());
SQLSRV_ASSERT(pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed");
sqlsrv_error_auto_ptr error;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, pdo_stmt->error_code, print_args);
// error_mode is valid because PDO API has already taken care of invalid ones
if (!warning && pdo_stmt->dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
pdo_sqlsrv_throw_exception(error);
}
ctx.set_last_error(error);
// return error ignored = true for warnings.
return (warning ? true : false);
}
// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements:
// 0, native code
// 1, native message
// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP')
void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval )
{
if( last_error ) {
// SQLSTATE is already present in the zval.
add_next_index_long( pdo_zval, last_error->native_code );
add_next_index_string( pdo_zval, reinterpret_cast<char*>( last_error->native_message ));
add_remaining_errors_to_array (last_error, pdo_zval);
}
}
// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool pdo_severity_check(_In_ unsigned int severity)
{
return ((severity & PDO_SQLSRV_G(pdo_log_severity)));
}
namespace {
// Workaround for name collision problem between the SQLSRV and PDO_SQLSRV drivers on Mac
// Place get_error_message into the anonymous namespace in pdo_util.cpp
sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code) {
sqlsrv_error_const *error_message = NULL;
int zr = (error_message = reinterpret_cast<sqlsrv_error_const*>(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE;
if (zr == FAILURE) {
DIE("get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code);
}
SQLSRV_ASSERT(error_message != NULL, "get_error_message: error_message was null");
return error_message;
}
void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error)
{
zval ex_obj;
ZVAL_UNDEF( &ex_obj );
zend_class_entry* ex_class = php_pdo_get_exception();
int zr = object_init_ex( &ex_obj, ex_class );
SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" );
#if PHP_VERSION_ID >= 80000
zend_object *zendobj = Z_OBJ_P(&ex_obj);
#endif
sqlsrv_malloc_auto_ptr<char> ex_msg;
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);
#if PHP_VERSION_ID < 80000
zend_update_property_string(ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof(EXCEPTION_PROPERTY_MSG) - 1, ex_msg);
zend_update_property_string(ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof(EXCEPTION_PROPERTY_CODE) - 1, reinterpret_cast<char*>(error->sqlstate));
#else
zend_update_property_string(ex_class, zendobj, EXCEPTION_PROPERTY_MSG, sizeof(EXCEPTION_PROPERTY_MSG) - 1, ex_msg);
zend_update_property_string(ex_class, zendobj, EXCEPTION_PROPERTY_CODE, sizeof(EXCEPTION_PROPERTY_CODE) - 1, reinterpret_cast<char*>(error->sqlstate));
#endif
zval ex_error_info;
ZVAL_UNDEF( &ex_error_info );
array_init( &ex_error_info );
add_next_index_string( &ex_error_info, reinterpret_cast<char*>( error->sqlstate ));
add_next_index_long( &ex_error_info, error->native_code );
add_next_index_string( &ex_error_info, reinterpret_cast<char*>( error->native_message ));
add_remaining_errors_to_array (error, &ex_error_info);
//zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info )
//and the refcount of the zend_array is incremented by 1
#if PHP_VERSION_ID < 80000
zend_update_property(ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof(EXCEPTION_PROPERTY_ERRORINFO) - 1, &ex_error_info);
#else
zend_update_property(ex_class, zendobj, EXCEPTION_PROPERTY_ERRORINFO, sizeof(EXCEPTION_PROPERTY_ERRORINFO) - 1, &ex_error_info);
#endif
//DELREF ex_error_info here to decrement the refcount of the zend_array is 1
//the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object;
//this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed
Z_DELREF( ex_error_info );
zend_throw_exception_object( &ex_obj );
}
void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z)
{
if (error->next != NULL && PDO_SQLSRV_G(report_additional_errors)) {
sqlsrv_error *p = error->next;
while (p != NULL) {
add_next_index_string(array_z, reinterpret_cast<char*>(p->sqlstate));
add_next_index_long(array_z, p->native_code);
add_next_index_string(array_z, reinterpret_cast<char*>(p->native_message));
p = p-> next;
}
}
}
void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args)
{
if (sqlsrv_error_code != SQLSRV_ERROR_ODBC) {
core_sqlsrv_format_driver_error(ctx, get_error_message(sqlsrv_error_code), error, SEV_ERROR, print_args);
strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast<const char*>(error->sqlstate));
}
else {
bool result = core_sqlsrv_get_odbc_error(ctx, 1, error, SEV_ERROR, true);
if (result) {
// Check if there exist more errors
int rec_number = 2;
sqlsrv_error_auto_ptr err;
sqlsrv_error *p = error;
do {
result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR, true);
if (result) {
p->next = err.get();
err.transferred();
p = p->next;
}
} while (result);
}
// core_sqlsrv_get_odbc_error() returns the error_code of size SQL_SQLSTATE_BUFSIZE,
// which is the same size as pdo_error_type
strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast<const char*>(error->sqlstate));
}
}
}

View file

@ -1,67 +0,0 @@
#ifndef PHP_PDO_SQLSRV_H
#define PHP_PDO_SQLSRV_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_pdo_sqlsrv.h
//
// Contents: Declarations for the extension
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
#include "php.h"
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
// request level variables
ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv)
unsigned int pdo_log_severity;
zend_long client_buffer_max_size;
short report_additional_errors;
#ifndef _WIN32
zend_long set_locale_info;
#endif
ZEND_END_MODULE_GLOBALS(pdo_sqlsrv)
ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv);
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// module global variables (initialized in minit and freed in mshutdown)
extern HashTable* g_pdo_errors_ht;
#define phpext_pdo_sqlsrv_ptr &g_pdo_sqlsrv_module_entry
// module initialization
PHP_MINIT_FUNCTION(pdo_sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(pdo_sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(pdo_sqlsrv);
extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP
#endif /* PHP_PDO_SQLSRV_H */

View file

@ -1,417 +0,0 @@
#ifndef PHP_PDO_SQLSRV_INT_H
#define PHP_PDO_SQLSRV_INT_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_pdo_sqlsrv_int.h
//
// Contents: Internal declarations for the extension
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
#include "core_sqlsrv.h"
#include "version.h"
extern "C" {
#include "pdo/php_pdo.h"
#include "pdo/php_pdo_driver.h"
}
#include <vector>
#include <map>
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
// henv context for creating connections
extern sqlsrv_context* g_pdo_henv_cp;
extern sqlsrv_context* g_pdo_henv_ncp;
// used for getting the version information
extern HMODULE g_sqlsrv_hmodule;
// macros used to access the global variables. Use these to make global variable access agnostic to threads
#ifdef ZTS
#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v)
#else
#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v
#endif
// INI settings and constants
// (these are defined as macros to allow concatenation as we do below)
#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size"
#define INI_PDO_SQLSRV_LOG "log_severity"
#define INI_PDO_SQLSRV_MORE_ERRORS "report_additional_errors"
#define INI_PREFIX "pdo_sqlsrv."
#ifndef _WIN32
#define INI_PDO_SET_LOCALE_INFO "set_locale_info"
#endif
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, pdo_log_severity,
zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong,
client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals )
STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SQLSRV_MORE_ERRORS, "1", PHP_INI_ALL, OnUpdateLong, report_additional_errors, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals)
#ifndef _WIN32
STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info,
zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals)
#endif
PHP_INI_END()
//*********************************************************************************************************************************
// Constants and Types
//*********************************************************************************************************************************
// sqlsrv driver specific PDO attributes
enum PDO_SQLSRV_ATTR {
// The custom attributes for this driver:
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
SQLSRV_ATTR_QUERY_TIMEOUT,
SQLSRV_ATTR_DIRECT_QUERY,
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
SQLSRV_ATTR_FETCHES_DATETIME_TYPE,
SQLSRV_ATTR_FORMAT_DECIMALS,
SQLSRV_ATTR_DECIMAL_PLACES,
SQLSRV_ATTR_DATA_CLASSIFICATION
};
// valid set of values for TransactionIsolation connection option
namespace PDOTxnIsolationValues {
const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED";
const char READ_COMMITTED[] = "READ_COMMITTED";
const char REPEATABLE_READ[] = "REPEATABLE_READ";
const char SERIALIZABLE[] = "SERIALIZABLE";
const char SNAPSHOT[] = "SNAPSHOT";
}
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// Basic string parser
class string_parser
{
protected:
const char* orig_str;
sqlsrv_context* ctx;
int len;
int pos;
unsigned int current_key;
HashTable* element_ht;
inline bool next(void);
inline bool is_eos(void);
inline bool is_white_space( _In_ char c );
bool discard_white_spaces(void);
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len );
};
//*********************************************************************************************************************************
// PDO DSN Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN connection string.
class conn_string_parser : private string_parser
{
enum States
{
FirstKeyValuePair,
Key,
Value,
ValueContent1,
ValueContent2,
RCBEncountered,
NextKeyValuePair,
};
private:
const char* current_key_name;
int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len );
void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len);
public:
conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht );
void parse_conn_string( void );
};
//*********************************************************************************************************************************
// PDO Query Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN named placeholders.
class sql_string_parser : private string_parser
{
private:
bool is_placeholder_char(char);
public:
void add_key_int_value_pair( _In_ unsigned int value );
sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht);
void parse_sql_string(void);
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
extern const connection_option PDO_CONN_OPTS[];
int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options);
// a core layer pdo dbh object. This object inherits and overrides the statement factory
struct pdo_sqlsrv_dbh : public sqlsrv_conn {
zval* stmts;
bool direct_query;
long query_timeout;
zend_long client_buffer_max_size;
bool fetch_numeric;
bool fetch_datetime;
bool format_decimals;
short decimal_places;
short use_national_characters;
bool emulate_prepare;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver );
};
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_encoding : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_pdo_scrollable : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_direct_query : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_cursor_scroll_type : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_emulate_prepares : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_fetch_numeric : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_fetch_datetime : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods;
// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary
struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) :
sqlsrv_stmt( c, handle, e, drv ),
direct_query( false ),
direct_query_subst_string( NULL ),
direct_query_subst_string_len( 0 ),
placeholders(NULL),
bound_column_param_types( NULL ),
fetch_numeric( false ),
fetch_datetime( false )
{
pdo_sqlsrv_dbh* db = static_cast<pdo_sqlsrv_dbh*>( c );
direct_query = db->direct_query;
fetch_numeric = db->fetch_numeric;
fetch_datetime = db->fetch_datetime;
format_decimals = db->format_decimals;
decimal_places = db->decimal_places;
query_timeout = db->query_timeout;
}
virtual ~pdo_sqlsrv_stmt( void );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
// for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types
virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream );
bool direct_query; // flag set if the query should be executed directly or prepared
const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters
size_t direct_query_subst_string_len; // length of query string used for direct queries
HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare
pdo_param_type* bound_column_param_types;
bool fetch_numeric;
bool fetch_datetime;
};
//*********************************************************************************************************************************
// Error Handling Functions
//*********************************************************************************************************************************
// represents the mapping between an error_code and the corresponding error message.
struct pdo_error {
unsigned int error_code;
sqlsrv_error_const sqlsrv_error;
};
// called when an error occurs in the core layer. These routines are set as the error_callback in a
// context. The context is passed to this function since it contains the function
bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args );
bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args );
bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args );
// common routine to transfer a sqlsrv_context's error to a PDO zval
void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval );
// reset the errors from the last operation
inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh )
{
strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error
// release the last statement from the dbh so that error handling won't have a statement passed to it
if( dbh->query_stmt ) {
dbh->query_stmt = NULL;
zval_ptr_dtor( &dbh->query_stmt_zval );
}
// if the driver isn't valid, just return (PDO calls close sometimes more than once?)
if( dbh->driver_data == NULL ) {
return;
}
// reset the last error on the sqlsrv_context
sqlsrv_context* ctx = static_cast<sqlsrv_conn*>( dbh->driver_data );
if( ctx->last_error() ) {
ctx->last_error().reset();
}
}
#define PDO_LOG_NOTICE(message) \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, message);
#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh );
inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt )
{
strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error
// if the driver isn't valid, just return (PDO calls close sometimes more than once?)
if( stmt->driver_data == NULL ) {
return;
}
// reset the last error on the sqlsrv_context
sqlsrv_context* ctx = static_cast<sqlsrv_stmt*>( stmt->driver_data );
if( ctx->last_error() ) {
ctx->last_error().reset();
}
}
#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt );
// validate the driver objects
#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); }
#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); }
//*********************************************************************************************************************************
// Utility Functions
//*********************************************************************************************************************************
// List of PDO specific error messages.
enum PDO_ERROR_CODES {
PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC,
PDO_SQLSRV_ERROR_INVALID_STMT_ATTR,
PDO_SQLSRV_ERROR_INVALID_ENCODING,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM,
PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED,
PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR,
PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR,
PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR,
PDO_SQLSRV_ERROR_INVALID_STMT_OPTION,
PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE,
PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED,
PDO_SQLSRV_ERROR_PARAM_PARSE,
PDO_SQLSRV_ERROR_LAST_INSERT_ID,
PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA,
PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING,
PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION,
PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE,
PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY,
PDO_SQLSRV_ERROR_INVALID_DSN_STRING,
PDO_SQLSRV_ERROR_INVALID_DSN_KEY,
PDO_SQLSRV_ERROR_INVALID_DSN_VALUE,
PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED,
PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY,
PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING,
SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN,
PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE,
PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY,
PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX,
PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE,
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID
};
extern pdo_error PDO_ERRORS[];
#define THROW_PDO_ERROR( ctx, custom, ... ) \
call_error_handler( ctx, custom, false, ## __VA_ARGS__ ); \
throw pdo::PDOException();
namespace pdo {
// an error which occurred in our PDO driver, NOT an exception thrown by PDO
struct PDOException : public core::CoreException {
PDOException() : CoreException()
{
}
};
} // namespace pdo
// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool pdo_severity_check(_In_ unsigned int severity);
#endif /* PHP_PDO_SQLSRV_INT_H */

View file

@ -1,83 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: template.rc
//
// Contents: Version resource
//
// Microsoft Drivers 5.10 for PHP for SQL Server
// 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,
// 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
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
#ifdef APSTUDIO_INVOKED
# error dont edit with MSVC
#endif
#include "winresrc.h"
#include "main/php_version.h"
#include "shared/version.h"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifndef THANKS_GUYS
# define THANKS_GUYS ""
#endif
#ifdef WANT_LOGO
0 ICON win32\build\php.ico
#endif
#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build
#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel
#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build)
#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel)
//Version
VS_VERSION_INFO VERSIONINFO
FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH, SQLVERSION_BUILD
PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0"
VALUE "CompanyName", "Microsoft Corp.\0"
VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0"
VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH, SQLVERSION_BUILD)
VALUE "InternalName", FILE_NAME "\0"
VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0"
VALUE "OriginalFilename", FILE_NAME "\0"
VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0"
VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH)
VALUE "URL", "http://www.microsoft.com\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#ifdef MC_INCLUDE
#include MC_INCLUDE
#endif

View file

@ -1,41 +0,0 @@
## Setup Environment on a clean machine
The PHP zip file can be downloaded from <http://php.net/downloads.php>.
The SQLSRV and PDO_SQLSRV driver binaries can be downloaded from <https://github.com/Microsoft/msphpsql/releases>.
### Windows
Install Visual Studio 2015 redistributable before running the following commands.
Run `Windows PowerShell` as administrator.
Set-ExecutionPolicy Unrestricted
.\setup_env_windows.ps1 <absolute path to the PHP zip file> <absolute path to the SQLSRV driver DLL> <absolute path to the PDO_SQLSRV driver DLL>
### Ubuntu 16
sudo env "PATH=$PATH" bash setup_env_unix.sh Ubuntu16 <PHP_VERSION - 7.x.y> <PHP_THREAD - ts or nts> <absolute path to the SQLSRV driver so> <absolute path to the PDO_SQLSRV driver so>
### RedHat 7
sudo env "PATH=$PATH" bash setup_env_unix.sh RedHat7 <PHP_VERSION - 7.x.y> <PHP_THREAD - ts or nts> <absolute path to the SQLSRV driver so> <absolute path to the PDO_SQLSRV driver so>
### SUSE 12
sudo env "PATH=$PATH" bash setup_env_unix.sh SUSE12 <PHP_VERSION - 7.x.y> <PHP_THREAD - ts or nts> <absolute path to the SQLSRV driver so> <absolute path to the PDO_SQLSRV driver so>
### MacOS 10.12 Sierra
`brew` cannot be run with `sudo` on Sierra. Either enable passwordless `sudo` on the machine or enter the password when prompted.
bash setup_env_unix.sh Sierra <PHP_VERSION - 7.x.y> <PHP_THREAD - ts or nts> <absolute path to the SQLSRV driver so> <absolute path to the PDO_SQLSRV driver so>
## Run benchmarks
PHPBench is used to run the benchmarks. Visit http://phpbench.readthedocs.io/en/latest/introduction.html to have an idea how the tool works.
##### 1. Modify lib/connect.php with the test database credentials
##### 2. Modify lib/result_db.php with the result database credentials
##### 3. The number of iterations for each test can be modified in the test itself (e.g., in test/Performance/benchmark/sqlsrv/SqlsrvSelectVersionBench.php). Each bench class in a test, or the parent class that it extends from, has a @Iteration(n) annotation. If you change the number in this annotation, you will change the number of iterations run for this test. By default, most tests are set to 1000 iterations.
##### 4. Execute run-perf_tests.py.
### Windows
py.exe run-perf_tests.py -platform <PLATFORM>
### Linux and Mac
On Linux and Mac, the script must be executed with `sudo python3` because to enable pooling it needs to modify odbcinst.ini system file. As an improvement, the location of the odbcinst.ini file can be changed so that sudo is not required.
python3 run-perf_tests.py -platform <PLATFORM> | tee run-perf_output.txt
`-platform` - The platform that the tests are ran on. Must be one of the following: Windows10, WindowsServer2016, WindowsServer2012, Ubuntu16, RedHat7, SUSE12, Sierra.
`-php-driver` (optional) - The driver that the tests are ran on. Must be one of the following: sqlsrv, pdo_sqlsrv, or both. Default is both.
`-testname` (optional) - The test to run. Must be the file name (not including path) of one test or 'all'. Default is 'all'. If one test is specified, must also specify the -php-driver option to sqlsrv or pdo_sqlsrv.

View file

@ -1,79 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "generateUpdateValues", "generateUpdateParams"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class PDOSqlsrvCRUDBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
private $updateValues;
private $updateParams;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable()
{
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function generateUpdateValues()
{
$this->updateValues = PDOSqlsrvUtil::generateUpdateValues();
}
public function generateUpdateParams()
{
$this->updateParams = PDOSqlsrvUtil::generateUpdateParams();
}
/**
* Each iteration does the following $loopsPerCRUDIter times:
* (i) insert a row into the table with insertWithPrepare
* (ii) fetch the row with fetchWithPrepare
* (iii) update the row's contents with updateWithPrepare
* (iv) delete the row with delete
* Every insertion calls prepare, bindParam and execute APIs.
* Every fetch calls prepare, execute and fetch APIs.
* Every update calls prepare, bindParam and execute APIs.
* Every delete calls prepare and execute APIs.
*/
public function benchCRUDWithPrepare()
{
for( $i=0; $i<PDOSqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
PDOSqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
PDOSqlsrvUtil::updateWithPrepare( $this->conn, $this->tableName, $this->updateValues, $this->updateParams );
PDOSqlsrvUtil::deleteWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable()
{
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,17 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
class PDOConnectionBench extends CRUDBaseBenchmark
{
/*
* Opens a connection and closes it immediately
*/
public function benchConnectAndDisconnect()
{
$conn = PDOSqlsrvUtil::connect();
PDOSqlsrvUtil::disconnect( $conn );
}
}
?>

View file

@ -1,35 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class PDOCreateDbTableProcBench extends CRUDBaseBenchmark
{
private $conn;
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
/*
* Each iteration creates a database, a table and a stored procedure in that database and drops the database at the end.
* Note that, execDirect function are used to execute all the queries.
*/
public function benchCreateDbTableProc()
{
$randomNum = rand();
$databaseName = "test_db_$randomNum";
$tableName = "test_table_$randomNum";
$procName = "test_proc_$randomNum";
PDOSqlsrvUtil::createDbTableProc( $this->conn, $databaseName, $tableName, $procName );
PDOSqlsrvUtil::dropDatabase( $this->conn, $databaseName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,65 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({ "dropTable","disconnect"})
*/
class PDODeleteBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable()
{
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare()
{
for( $i=0; $i<PDOSqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
/**
* Each iteration inserts 1000 rows into the table, benchDelete deletes top row from the table 1000 times.
* Note that, every delete calls prepare and execute APIs.
*/
public function benchDelete()
{
for( $i=0; $i<PDOSqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
PDOSqlsrvUtil::deleteWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable()
{
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,61 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({"dropTable", "disconnect"})
*/
class PDOFetchBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable()
{
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare()
{
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
/**
* Each iteration inserts a row into the table, benchFetchWithPrepare() fetches that row 1000 times.
* Note that, every fetch calls prepare, execute and fetch APIs.
*/
public function benchFetchWithPrepare()
{
for( $i=0; $i<PDOSqlsrvUtil::$loopsPerCRUDIter; $i++)
{
PDOSqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable()
{
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,37 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @Iterations(1)
* @BeforeMethods({"connect", "setTableName" })
* @AfterMethods({ "disconnect"})
*/
class PDOFetchLargeBench
{
private $conn;
private $tableName;
public function setTableName()
{
//Assumes the table is already populated with data
$this->tableName = "LargeDB.dbo.datatypes";
}
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
/*
* Each iteration calls prepare, execute and fetch APIs to fetch the already populated data
*/
public function benchFetchWithPrepare()
{
PDOSqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,55 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class PDOInsertBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable()
{
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
/**
* Each iteration inserts 1000 rows into the table.
* Note that, every insertion calls prepare, bindParam and execute APIs.
*/
public function benchInsertWithPrepare()
{
for ( $i=0; $i<PDOSqlsrvUtil::$loopsPerCRUDIter; $i++)
{
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
public function dropTable()
{
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,30 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @Iterations(10000)
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class PDOSelectVersionBench
{
private $conn;
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
/*
* Each iteration calls execDirect API to fetch @@Version
*/
public function benchSelectVersion()
{
$version = PDOSqlsrvUtil::selectVersion( $this->conn );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,74 +0,0 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare", "generateUpdateValues", "generateUpdateParams"})
* @AfterMethods({"dropTable", "disconnect"})
*/
class PDOUpdateBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
private $updateValues;
private $updateParams;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable()
{
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare()
{
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
public function generateUpdateValues()
{
$this->updateValues = PDOSqlsrvUtil::generateUpdateValues();
}
public function generateUpdateParams()
{
$this->updateParams = PDOSqlsrvUtil::generateUpdateParams();
}
/**
* Each iteration inserts a row into the table, updateWithPrepare() updates that row 1000 times.
* Note that, every update calls prepare, bindParam and execute APIs.
*/
public function benchUpdateWithPrepare()
{
for( $i=0; $i<PDOSqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
PDOSqlsrvUtil::updateWithPrepare( $this->conn, $this->tableName, $this->updateValues, $this->updateParams );
}
}
public function dropTable()
{
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,79 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "generateUpdateValues", "generateUpdateParams"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvCRUDBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
private $updateValues;
private $updateParams;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
public function createTable()
{
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function generateUpdateValues()
{
$this->updateValues = SqlsrvUtil::generateUpdateValues();
}
public function generateUpdateParams()
{
$this->updateParams = SqlsrvUtil::generateUpdateParams();
}
/**
* Each iteration does the following $loopsPerCRUDIter times:
* (i) insert a row into the table with insertWithPrepare
* (ii) fetch the row with fetchWithPrepare
* (iii) update the row's contents with updateWithPrepare
* (iv) delete the row with delete
* Every insertion calls prepare, bindParam and execute APIs.
* Every fetch calls prepare, execute and fetch APIs.
* Every update calls prepare, bindParam and execute APIs.
* Every delete calls prepare and execute APIs.
*/
public function benchCRUDWithPrepare()
{
for( $i=0; $i<SqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
SqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
SqlsrvUtil::updateWithPrepare( $this->conn, $this->tableName, $this->updateValues, $this->updateParams );
SqlsrvUtil::delete( $this->conn, $this->tableName );
}
}
public function dropTable()
{
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,16 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
class SqlsrvConnectionBench extends CRUDBaseBenchmark
{
/*
* Opens a connection and closes it immediately
*/
public function benchConnectAndDisconnect()
{
$conn = SqlsrvUtil::connect();
SqlsrvUtil::disconnect( $conn );
}
}

View file

@ -1,36 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class SqlsrvCreateDbTableProcBench extends CRUDBaseBenchmark
{
private $conn;
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
/*
* Each iteration creates a database, a table and a stored procedure in that database and drops the database at the end.
* Note that, ODBC SQLExecDirect function are used to execute all the queries.
*/
public function benchCreateDbTableProc()
{
$randomNum = rand();
$databaseName = "test_db_$randomNum";
$tableName = "test_table_$randomNum";
$procName = "test_proc_$randomNum";
SqlsrvUtil::createDbTableProc( $this->conn, $databaseName, $tableName, $procName );
SqlsrvUtil::dropDatabase( $this->conn, $databaseName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,65 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvDeleteBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
public function createTable()
{
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare()
{
for ( $i=0; $i<SqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
/**
* Each iteration inserts 1000 rows into the table, benchDelete deletes top row from the table 1000 times.
* Note that, every delete calls prepare and execute APIs.
*/
public function benchDelete()
{
for( $i=0; $i<SqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
SqlsrvUtil::delete( $this->conn, $this->tableName );
}
}
public function dropTable()
{
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,62 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvFetchBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
public function createTable()
{
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare()
{
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
/**
* Each iteration inserts a row into the table, benchFetchWithPrepare() fetches that row 1000 times.
* Note that, every fetch calls prepare, execute and fetch APIs.
*/
public function benchFetchWithPrepare()
{
for( $i=0; $i<SqlsrvUtil::$loopsPerCRUDIter; $i++)
{
SqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable()
{
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,37 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @Iterations(1)
* @BeforeMethods({"connect", "setTableName" })
* @AfterMethods({ "disconnect"})
*/
class SqlsrvFetchLargeBench
{
private $conn;
private $tableName;
public function setTableName()
{
//Assumes the table is already populated with data
$this->tableName = "LargeDB.dbo.datatypes";
}
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
/*
* Each iteration calls prepare, execute and fetch APIs to fetch the already populated data
*/
public function benchFetchWithPrepare()
{
SqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,56 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues"})
* @AfterMethods({"dropTable","disconnect"})
*/
class SqlsrvInsertBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
public function createTable()
{
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
/**
* Each iteration inserts 1000 rows into the table.
* Note that, every insertion calls prepare, bindParam and execute APIs.
*/
public function benchInsertWithPrepare()
{
for( $i=0; $i<SqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
public function dropTable()
{
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,30 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @Iterations(10000)
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class SqlsrvSelectVersionBench
{
private $conn;
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
/*
* Each iteration calls execDirect API to fetch @@Version
*/
public function benchSelectVersion()
{
$version = SqlsrvUtil::selectVersion( $this->conn );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,74 +0,0 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
include_once __DIR__ . "/../../lib/CRUDBaseBenchmark.php";
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare", "generateUpdateValues", "generateUpdateParams"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvUpdateBench extends CRUDBaseBenchmark
{
private $conn;
private $tableName;
private $insertValues;
private $updateValues;
private $updateParams;
public function setTableName()
{
$this->tableName = "datatypes_".rand();
}
public function connect()
{
$this->conn = SqlsrvUtil::connect();
}
public function createTable()
{
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues()
{
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare()
{
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
public function generateUpdateValues()
{
$this->updateValues = SqlsrvUtil::generateUpdateValues();
}
public function generateUpdateParams()
{
$this->updateParams = SqlsrvUtil::generateUpdateParams();
}
/**
* Each iteration inserts a row into the table, updateWithPrepare() updates that row 1000 times.
* Note that, every update calls prepare, bindParam and execute APIs.
*/
public function benchUpdateWithPrepare()
{
for( $i=0; $i<SqlsrvUtil::$loopsPerCRUDIter; $i++ )
{
SqlsrvUtil::updateWithPrepare( $this->conn, $this->tableName, $this->updateValues, $this->updateParams );
}
}
public function dropTable()
{
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect()
{
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -1,12 +0,0 @@
{
"name": "Benchmark - PHP Drivers for SQL Server",
"require-dev": {
"phpbench/phpbench": "^1.0@dev"
},
"autoload": {
"psr-4": {
"SqlsrvPerfTest\\": "lib",
"PDOSqlsrvPerfTest\\": "lib"
}
}
}

View file

@ -1,9 +0,0 @@
<?php
/**
* @Iterations(1000)
*/
abstract class CRUDBaseBenchmark
{
}
?>

View file

@ -1,267 +0,0 @@
<?php
namespace PDOSqlsrvPerfTest;
use PDO;
class PDOSqlsrvUtil
{
public static $loopsPerCRUDIter = 100;
public static function connect()
{
require dirname(__FILE__).DIRECTORY_SEPARATOR.'connect.php';
try
{
$conn = new PDO( "sqlsrv:Server=$server; Database=$database; ConnectionPooling=$pooling; MultipleActiveResultSets=$mars" , $uid, $pwd );
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
}
catch( PDOException $e )
{
var_dump( $e );
exit;
}
}
public static function disconnect( $conn )
{
$conn = null;
}
public static function selectVersion( $conn )
{
$sql = "SELECT @@Version";
$stmt = self::query( $conn, $sql );
return self::fetch( $stmt );
}
public static function createDbTableProc( $conn, $databaseName, $tableName, $procName )
{
$tableParams = "id INTEGER, name VARCHAR(32), value INTEGER, start_date DATE, time_added TIMESTAMP, set_time TIME(7)";
$procParams = "@id INTEGER, @name VARCHAR(32)";
$procTextBase = "SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name";
self::createDatabase( $conn, $databaseName );
self::useDatabase( $conn, $databaseName );
self::createTable( $conn, $tableName, $tableParams );
self::createStoredProc( $conn, $procName, $procParams, $procTextBase );
}
public static function generateInsertValues()
{
$vcharVal = "test string";
$nvcharVal = "wstring";
$intVal = 3;
$dateTimeVal = '2016-10-31 01:39:39.7341976';
$charVal = "fixedstr";
$ncharVal = "fixed w string";
$realVal = 14.2;
$binVal = 0x0123456789ABCDE;
$vbinVal = 13;
$dateTimeOffsetVal = '7032-12-17 02:32:18.5210310+00:00';
$values = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $values;
}
public static function generateUpdateValues()
{
$vcharVal = "test string updated";
$nvcharVal = "wstring updated";
$intVal = 5;
$dateTimeVal = '2005-10-31 01:20:39.7341976';
$charVal = "fixedstr updated";
$ncharVal = "fixed w string updated";
$realVal = 19.2;
$binVal = 0x01345789ABCDE;
$vbinVal = 18;
$dateTimeOffsetVal = '1032-12-17 02:42:18.5210310+00:00';
$updatedValues = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $updatedValues;
}
public static function generateUpdateParams()
{
$fieldNames = array(
"vstring",
"nvstring",
"num",
"dttwo",
"string",
"nstring",
"real",
"bin",
"vbin",
"dtoffset");
$params = "";
foreach( $fieldNames as $fieldName )
{
$params = $params.$fieldName."=?,";
}
$params = rtrim($params,", ");
return $params;
}
public static function insertWithPrepare( $conn, $tableName, $values )
{
$sql = "INSERT INTO $tableName VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = self::prepare( $conn, $sql );
self::bindParams( $stmt, $values );
self::execute( $stmt );
}
public static function fetchWithPrepare( $conn, $tableName )
{
$sql = "SELECT * FROM $tableName";
$stmt = self::prepare( $conn, $sql );
self::execute( $stmt );
while ( $row = self::fetch( $stmt )){}
}
public static function deleteWithPrepare( $conn, $tableName )
{
$sql = "DELETE TOP (1) FROM $tableName";
$stmt = self::prepare( $conn, $sql );
self::execute( $stmt );
}
public static function updateWithPrepare( $conn, $tableName, $updateValues, $params )
{
$sql = "UPDATE $tableName SET ".$params;
$stmt = self::prepare( $conn, $sql );
self::bindParams( $stmt, $updateValues );
self::execute( $stmt );
}
private function bindParams( $stmt, $values )
{
//This functionn assumes the fields are from createCRUDTable()
self::bindParam( $stmt, 1, $values[0], PDO::PARAM_STR);
self::bindParam( $stmt, 2, $values[1], PDO::PARAM_STR);
self::bindParam( $stmt, 3, $values[2], PDO::PARAM_INT);
self::bindParam( $stmt, 4, $values[3], PDO::PARAM_STR);
self::bindParam( $stmt, 5, $values[4], PDO::PARAM_STR);
self::bindParam( $stmt, 6, $values[5], PDO::PARAM_STR);
self::bindParam( $stmt, 7, $values[6], PDO::PARAM_STR);
self::bindParam( $stmt, 8, $values[7], PDO::PARAM_LOB);
self::bindParam( $stmt, 9, $values[8], PDO::PARAM_LOB);
self::bindParam( $stmt, 10, $values[9], PDO::PARAM_STR);
}
public static function createCRUDTable( $conn, $tableName )
{
$fields = array(
"vstring" => "VARCHAR(64)",
"nvstring" => "NVARCHAR(64)",
"num" => "int",
"dttwo" => "DATETIME2",
"string" => "CHAR(64)",
"nstring" => "NCHAR(64)",
"real" => "NUMERIC",
"bin" => "BINARY(16)",
"vbin" => "VARBINARY",
"dtoffset" => "DATETIMEOFFSET");
$params = "";
foreach( $fields as $fieldName => $type )
{
$params .= $fieldName." ".$type.",";
}
$params = rtrim($params,", ");
self::createTable( $conn, $tableName, $params );
}
private function createDatabase( $conn, $dbName )
{
$sql = "CREATE DATABASE $dbName";
$conn->exec( $sql );
}
public static function dropDatabase( $conn, $dbName )
{
$sql = "USE MASTER;DROP DATABASE $dbName";
$conn->exec( $sql );
}
public static function createTable( $conn, $tableName, $params )
{
$sql = "CREATE TABLE $tableName ($params)";
$conn->exec( $sql );
}
public static function dropTable( $conn, $tableName )
{
$sql = "DROP TABLE $tableName";
$conn->exec( $sql );
}
private function useDatabase( $conn, $dbName )
{
$sql = "USE $dbName";
$conn->exec( $sql );
}
private function createStoredProc( $conn, $procName, $params, $text )
{
$sql = "CREATE PROCEDURE $procName $params AS $text";
$conn->exec( $sql );
}
private function dropStoredProc( $conn, $procName )
{
$sql = "DROP PROCEDURE $procName";
$conn->exec( $sql );
}
private function query( $conn, $sql )
{
try
{
return $conn->query( $sql );
}
catch( PDOException $e )
{
var_dump( $e );
exit;
}
}
private function fetch( $stmt )
{
return $stmt->fetch();
}
private function prepare( $conn, $sql )
{
try
{
$stmt = $conn->prepare( $sql );
if( $stmt === false )
{
die( "Failed to prepare\n");
}
return $stmt;
}
catch( PDOException $e )
{
var_dump( $e );
exit;
}
}
private function execute( $stmt )
{
$ret = $stmt->execute();
if( $ret === false )
{
die( "Failed to execute\n" );
}
}
private function bindParam( $stmt, $index, $value, $type )
{
$ret = $stmt->bindParam( $index, $value, $type );
if ( $ret === false)
{
die( "Faild to bind\n");
}
}
}

View file

@ -1,272 +0,0 @@
<?php
namespace SqlsrvPerfTest;
class SqlsrvUtil
{
public static $loopsPerCRUDIter = 100;
public static function connect()
{
require dirname(__FILE__).DIRECTORY_SEPARATOR.'connect.php';
$options = array( "Database"=>$database, "UID"=>$uid, "PWD"=>$pwd, "ConnectionPooling"=>$pooling, "MultipleActiveResultSets"=>$mars );
$conn = sqlsrv_connect( $server, $options );
if ( $conn === false )
{
die( print_r( sqlsrv_errors(), true));
}
return $conn;
}
public static function disconnect( $conn )
{
if ( $conn === false || $conn === null )
{
die( print_r( "Invalid connection resource\n"));
}
$ret = sqlsrv_close( $conn );
if ( $ret === false )
{
die( print_r( sqlsrv_errors(), true));
}
}
public static function selectVersion( $conn )
{
$sql = "SELECT @@Version";
$stmt = self::query( $conn, $sql );
return self::fetchArray( $stmt );
}
public static function createDbTableProc( $conn, $databaseName, $tableName, $procName )
{
$tableParams = "id INTEGER, name VARCHAR(32), value INTEGER, start_date DATE, time_added TIMESTAMP, set_time TIME(7)";
$procParams = "@id INTEGER, @name VARCHAR(32)";
$procTextBase = "SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name";
self::createDatabase( $conn, $databaseName );
self::useDatabase( $conn, $databaseName );
self::createTable( $conn, $tableName, $tableParams );
self::createStoredProc( $conn, $procName, $procParams, $procTextBase );
}
public static function generateInsertValues()
{
$vcharVal = "test string";
$nvcharVal = "wstring";
$intVal = 3;
$dateTimeVal = '2016-10-31 01:39:39.7341976';
$charVal = "fixedstr";
$ncharVal = "fixed w string";
$realVal = 14.2;
$binVal = 0x0123456789ABCDE;
$vbinVal = 13;
$dateTimeOffsetVal = '7032-12-17 02:32:18.5210310+00:00';
$values = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $values;
}
public static function generateUpdateValues()
{
$vcharVal = "test string updated";
$nvcharVal = "wstring updated";
$intVal = 5;
$dateTimeVal = '2005-10-31 01:20:39.7341976';
$charVal = "fixedstr updated";
$ncharVal = "fixed w string updated";
$realVal = 19.2;
$binVal = 0x01345789ABCDE;
$vbinVal = 18;
$dateTimeOffsetVal = '1032-12-17 02:42:18.5210310+00:00';
$updatedValues = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $updatedValues;
}
public static function generateUpdateParams()
{
$fieldNames = array(
"vstring",
"nvstring",
"num",
"dttwo",
"string",
"nstring",
"real",
"bin",
"vbin",
"dtoffset");
$params = "";
foreach( $fieldNames as $fieldName )
{
$params = $params.$fieldName."=?,";
}
$params = rtrim($params,", ");
return $params;
}
public static function insertWithPrepare( $conn, $tableName, $values )
{
$sql = "INSERT INTO $tableName VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = self::prepare( $conn, $sql, $values );
self::execute( $stmt );
}
public static function updateWithPrepare( $conn, $tableName, $updateValues, $params )
{
$sql = "UPDATE $tableName SET ".$params;
$stmt = self::prepare( $conn, $sql, $updateValues );
self::execute( $stmt );
}
public static function fetchWithPrepare( $conn, $tableName )
{
$sql = "SELECT * FROM $tableName";
$stmt = self::prepare( $conn, $sql, array());
self::execute( $stmt );
while( $row = self::fetchArray( $stmt ) ) {}
}
public static function createCRUDTable( $conn, $tableName )
{
$fields = array(
"vstring" => "VARCHAR(64)",
"nvstring" => "NVARCHAR(64)",
"num" => "int",
"dttwo" => "DATETIME2",
"string" => "CHAR(64)",
"nstring" => "NCHAR(64)",
"real" => "NUMERIC",
"bin" => "BINARY(16)",
"vbin" => "VARBINARY",
"dtoffset" => "DATETIMEOFFSET");
$params = "";
foreach( $fields as $fieldName => $type )
{
$params .= $fieldName." ".$type.",";
}
$params = rtrim($params,", ");
self::createTable( $conn, $tableName, $params );
}
public static function query( $conn, $sql )
{
$stmt = sqlsrv_query( $conn, $sql );
if( $stmt === false )
{
die( print_r( sqlsrv_errors(), true));
}
return $stmt;
}
public static function fetch( $stmt )
{
$ret = sqlsrv_fetch( $stmt );
if( $ret === false )
{
die( print_r( sqlsrv_errors(), true));
}
return $ret;
}
public static function fetchArray( $stmt )
{
$row = sqlsrv_fetch_array( $stmt );
if ( $row === false )
{
die( print_r( sqlsrv_errors(), true));
}
return $row;
}
public static function getField( $stmt, $index )
{
return sqlsrv_get_field( $stmt, $index );
}
private function createDatabase( $conn, $dbName )
{
$sql = "CREATE DATABASE $dbName";
self::query( $conn, $sql );
}
public static function dropDatabase( $conn, $dbName )
{
$sql = "USE MASTER;DROP DATABASE $dbName";
self::query( $conn, $sql );
}
public static function createTable( $conn, $tableName, $params )
{
$sql = "CREATE TABLE $tableName ($params)";
self::query( $conn, $sql );
}
public static function dropTable( $conn, $tableName )
{
$sql = "DROP TABLE $tableName";
self::query( $conn, $sql );
}
private function useDatabase( $conn, $dbName )
{
$sql = "USE $dbName";
self::query( $conn, $sql );
}
private function createStoredProc( $conn, $procName, $params, $text )
{
$sql = "CREATE PROCEDURE $procName $params AS $text";
self::query( $conn, $sql );
}
private function dropStoredProc( $conn, $procName )
{
$sql = "DROP PROCEDURE $procName";
self::query( $conn, $sql );
}
private function insert( $conn, $tableName, $values )
{
$sql = "INSERT INTO $tableName values ($values)";
self::query( $conn, $sql );
}
private function update( $conn, $tableName, $params, $condition )
{
$sql = "UPDATE $tableName SET $params WHERE $condition";
self::query( $sql );
}
public function delete( $conn, $tableName)
{
$sql = "DELETE TOP (1) FROM $tableName";
self::query( $conn, $sql );
}
public function deleteWithPrepare( $conn, $tableName )
{
$sql = "DELETE TOP (1) FROM $tableName";
$stmt = self::prepare( $conn, $sql, array() );
self::execute( $stmt );
}
private function prepare( $conn, $sql, $params )
{
$stmt = sqlsrv_prepare( $conn, $sql, $params );
if( $stmt === false )
{
die( print_r( sqlsrv_errors(), true));
}
return $stmt;
}
public function execute( $stmt )
{
$ret = sqlsrv_execute( $stmt );
if ( $ret === false )
{
die( print_r( sqlsrv_errors(), true));
}
}
}

View file

@ -1,8 +0,0 @@
<?php
$server = 'server';
$database = 'testdb';
$uid = 'usr';
$pwd = 'pwd';
$pooling=false;
$mars=false;
?>

View file

@ -1,6 +0,0 @@
<?php
$server = 'server';
$database = 'testdb';
$uid = 'usr';
$pwd = 'pwd';
?>

View file

@ -1,139 +0,0 @@
# Performance Test Results for the Microsoft Drivers for PHP for SQL Server
This page lists benchmarking results for both the SQLSRV and PDO_SQLSRV drivers for various operations and various environments. Test are performed on the following client environment:
* PHP 7.1.7
* Driver version 5.0.0
* Platform: x64
* Non-thread safe
* ODBC Driver version 13.1
The client OS includes:
* Windows Server 2016
* MacOS Sierra
* Red Hat 7.2
* Ubuntu 16.04
* SUSE 12
The server testing environment is either
* Windows Server 2016 with SQL Server 2016, or
* Ubuntu 16.04 with SQL Server Linux 2016
The following table lists benchmarking results for each scenario tested. Listed are the times taken for all iterations, and the maximum memory usage for all iterations in each scenario.
| | | | SQLSRV | | | PDO_SQLSRV | | |
| :----- | :----- | :----- | :----- | -----: | -----: | :-----| -----: | -----: |
|Scenario|Server|Client|Iterations|Memory(MB)|Time(s)|Iterations|Memory(MB)|Time(s)|
|CRUD |SQL Server Windows|Windows Server 2016|1000x100 rows|0.88667|1074|1000x100 rows|0.88562|1195|
| | |OS X Sierra |1000x100 rows|0.88781|941 |1000x100 rows|0.88678|1019|
| | |Red Hat 7.2 |1000x100 rows|0.88223|1053|1000x100 rows|0.88120|1164|
| | |Ubuntu 16.04 |1000x100 rows|0.88056|1059|1000x100 rows|0.87954|1172|
| | |Suse 12 |1000x100 rows|0.88945|978 |1000x100 rows|0.88849|1084|
| |SQL Server Linux |Windows Server 2016| | | | | | |
| | |OS X Sierra |1000x100 rows|0.88781|1112|1000x100 rows|0.88678|1210|
| | |Red Hat 7.2 |1000x100 rows|0.88223|1189|1000x100 rows|0.88120|1315|
| | |Ubuntu 16.04 |1000x100 rows|0.88056|1199| | | |
| | |Suse 12 | | | | | | |
|CRUD Insert|SQL Server Windows|Windows Server 2016|1000x100 rows|0.88380|311|1000x100 rows|0.88264|319|
| | |OS X Sierra |1000x100 rows|0.88770|280|1000x100 rows|0.88660|282|
| | |Red Hat 7.2 |1000x100 rows|0.88342|304|1000x100 rows|0.88229|309|
| | |Ubuntu 16.04 |1000x100 rows|0.88171|309|1000x100 rows|0.88065|357|
| | |Suse 12 |1000x100 rows|0.88655|285|1000x100 rows|0.88548|290|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88681|368|1000x100 rows|0.88568|387|
| | |OS X Sierra |1000x100 rows|0.88770|348|1000x100 rows|0.88660|359|
| | |Red Hat 7.2 |1000x100 rows|0.88342|349|1000x100 rows|0.88230|358|
| | |Ubuntu 16.04 |1000x100 rows|0.88179|357|1000x100 rows|0.88067|363|
| | |Suse 12 |1000x100 rows|0.88655|342|1000x100 rows|0.88548|344|
|CRUD Fetch |SQL Server Windows|Windows Server 2016|1000x100 rows|0.88431|255|1000x100 rows|0.88318|250|
| | |OS X Sierra |1000x100 rows|0.88828|188|1000x100 rows|0.88718|187|
| | |Red Hat 7.2 |1000x100 rows|0.88396|248|1000x100 rows|0.88284|245|
| | |Ubuntu 16.04 |1000x100 rows|0.88226|250|1000x100 rows|0.87514|247|
| | |Suse 12 |1000x100 rows|0.88709|228|1000x100 rows|0.88602|227|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88735|287|1000x100 rows|0.88622|282|
| | |OS X Sierra |1000x100 rows|0.88828|229|1000x100 rows|0.88718|228|
| | |Red Hat 7.2 |1000x100 rows|0.88400|272|1000x100 rows|0.88287|270|
| | |Ubuntu 16.04 |1000x100 rows|0.88236|276|1000x100 rows|0.88124|274|
| | |Suse 12 |1000x100 rows|0.88709|257|1000x100 rows|0.88602|254|
|CRUD Update|SQL Server Windows|Windows Server 2016|1000x100 rows|0.88594|314|1000x100 rows|0.88478|323|
| | |OS X Sierra |1000x100 rows|0.88988|284|1000x100 rows|0.88879|285|
| | |Red Hat 7.2 |1000x100 rows|0.88560|307|1000x100 rows|0.88448|313|
| | |Ubuntu 16.04 |1000x100 rows|0.87787|310|1000x100 rows|0.88235|327|
| | |Suse 12 |1000x100 rows|0.88869|288|1000x100 rows|0.88762|294|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88899|369|1000x100 rows|0.88786|376|
| | |OS X Sierra |1000x100 rows|0.88988|349|1000x100 rows|0.88879|362|
| | |Red Hat 7.2 |1000x100 rows|0.88560|357|1000x100 rows|0.88448|362|
| | |Ubuntu 16.04 |1000x100 rows|0.88397|361|1000x100 rows|0.88285|367|
| | |Suse 12 |1000x100 rows|0.88873|343|1000x100 rows|0.88766|340|
|CRUD Delete|SQL Server Windows|Windows Server 2016|1000x100 rows|0.88452|197|1000x100 rows|0.88341|303|
| | |OS X Sierra |1000x100 rows|0.88843|188|1000x100 rows|0.88737|274|
| | |Red Hat 7.2 |1000x100 rows|0.88411|192|1000x100 rows|0.88303|295|
| | |Ubuntu 16.04 |1000x100 rows|0.87638|193|1000x100 rows|0.88094|298|
| | |Suse 12 |1000x100 rows|0.88728|181|1000x100 rows|0.88625|277|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88754|233|1000x100 rows|0.88645|358|
| | |OS X Sierra |1000x100 rows|0.88843|243|1000x100 rows|0.88737|340|
| | |Red Hat 7.2 |1000x100 rows|0.88415|229|1000x100 rows|0.88307|342|
| | |Ubuntu 16.04 |1000x100 rows|0.88251|229|1000x100 rows|0.88144|347|
| | |Suse 12 |1000x100 rows|0.88728|220|1000x100 rows|0.88625|328|
|Large Read - 10,000,000 rows |SQL Server Windows|Windows Server 2016|1|0.88117|606|1|0.88003|382|
| | |OS X Sierra |1|0.88572|420|1|0.88463|344|
| | |Red Hat 7.2 |1|0.88145|495|1|0.88035|694|
| | |Ubuntu 16.04 |1|0.87981|413|1|0.87259|585|
| | |Suse 12 |1|0.88397|453|1|0.88291|326|
| |SQL Server Linux |Windows Server 2016|1|0.88423|685| | | |
| | |OS X Sierra |1|0.88571|308| | | |
| | |Red Hat 7.2 |1|0.88140|512| | | |
| | |Ubuntu 16.04 |1|0.87977|749| | | |
| | |Suse 12 |1|0.88397|828| | | |
|SELECT @@Version|SQL Server Windows|Windows Server 2016|10,000|0.88074|16|10,000|0.87955|28|
| | |OS X Sierra |10,000|0.88522|19|10,000|0.88412|33|
| | |Red Hat 7.2 |10,000|0.88118|11|10,000|0.88006|22|
| | |Ubuntu 16.04 | | | | | | |
| | |Suse 12 |10,000|0.88351|13|10,000|0.88243|24|
| |SQL Server Linux |Windows Server 2016|10,000|0.88377|16|10,000|0.88264|30|
| | |OS X Sierra |10,000|0.88522|19|10,000|0.88412|33|
| | |Red Hat 7.2 |10,000|0.88094|17|10,000|0.87981|28|
| | |Ubuntu 16.04 |10,000|0.87930|16|10,000|0.87818|26|
| | |Suse 12 |10,000|0.88351|15|10,000|0.88243|26|
|CREATE DATABASE/TABLE/STORED PROCS|SQL Server Windows|Windows Server 2016|1000|0.88237| 515|1000|0.88123| 529|
| | |OS X Sierra |1000|0.88638| 420|1000|0.88530| 457|
| | |Red Hat 7.2 |1000|0.88213| 434|1000|0.88102| 447|
| | |Ubuntu 16.04 |1000|0.87437| 395| | | |
| | |Suse 12 |1000|0.88512| 494|1000|0.88406| 507|
| |SQL Server Linux |Windows Server 2016|1000|0.88544|2285|1000|0.88433|2324|
| | |OS X Sierra |1000|0.88638|2338|1000|0.88530|2352|
| | |Red Hat 7.2 |1000|0.88216|2292|1000|0.88106|2312|
| | |Ubuntu 16.04 |1000|0.80530|2340|1000|0.87943|1228|
| | |Suse 12 |1000|0.88512|2341|1000|0.88406|2337|
|Open and close 1000 connections|SQL Server Windows|Windows Server 2016|1000|0.87956|39|1000|0.87843|38|
| | |OS X Sierra |1000|0.88363|62|1000|0.88255|62|
| | |Red Hat 7.2 |1000|0.87935|30|1000|0.87825|30|
| | |Ubuntu 16.04 | | | |1000|0.87608|29|
| | |Suse 12 |1000|0.88232|24|1000|0.88126|25|
| |SQL Server Linux |Windows Server 2016|1000|0.88267|45|1000|0.88156|45|
| | |OS X Sierra |1000|0.88363|65|1000|0.88255|65|
| | |Red Hat 7.2 |1000|0.87932|35|1000|0.87822|35|
| | |Ubuntu 16.04 |1000|0.87771|35|1000|0.87659|34|
| | |Suse 12 |1000|0.88232|29|1000|0.88126|30|
|Open and close 1000 connections with connection pooling|SQL Server Windows|Windows Server 2016|||||||
| | |OS X Sierra |||||||
| | |Red Hat 7.2 |||||||
| | |Ubuntu 16.04 |||||||
| | |Suse 12 |||||||
| |SQL Server Linux |Windows Server 2016|||||||
| | |OS X Sierra |||||||
| | |Red Hat 7.2 |||||||
| | |Ubuntu 16.04 |||||||
| | |Suse 12 |||||||
The following table lists details of each scenario tested, including the SQL statements and data types used.
|Scenario|Description|Iterations|Operations/Iteration|t-SQL|Column Datatypes|
|---|---|---|---|---|---|
|CRUD|Contains a loop for inserting a row into, fetching from, updating a row in, and deleting a row from a table. Measurement starts immediately before preparing the first INSERT INTO statement and ends immediately after the 100th DELETE statement executes.|1000|100|<ul><li>INSERT INTO &lt;tableName&gt; VALUES (&lt;params&gt;)</li><li>SELECT \* FROM &lt;tableName&gt;</li><li>UPDATE &lt;tableName&gt; SET &lt;params&gt;</li><li>DELETE TOP(1) FROM &lt;tableName&gt;</li></ul>|<ul><li>VARCHAR(64)</li><li>NVARCHAR(64)</li><li>INT</li><li>DATETIME2</li><li>CHAR(64)</li><li>NCHAR(64)</li><li>NUMERIC</li><li>BINARY(64)</li><li>VARBINARY</li><li>DATETIMEOFFSET</li></ul>|
|CRUD Insert|Contains a loop for inserting into a table. Each iteration prepares a statement, binds params, and executes. Measurement starts immediately before preparing the first INSERT INTO statement and ends immediately after the 100th statement executes.|1000|100|INSERT INTO &lt;tableName&gt; VALUES (&lt;params&gt;)||
|CRUD Fetch|Contains a loop for fetching from a table with data inserted using the t-SQL in CRUD Insert. Each iteration prepares and executes a statement, and fetches a row from the result set. Measurement starts immediately before preparing the first SELECT statmeent and ends immediately after the 100th fetch.|1000|100|SELECT \* FROM &lt;tableName&gt;|
|CRUD Update|Contains a loop for updating a table with one row populated using the t-SQL in CRUD Insert. Each iteration prepares a statement, binds params, and executes. Measurement starts immediately before preparing the 1st UPDATE statement and ends immediately after the 100th statement executes.|1000|100|UPDATE &lt;tableName&gt; SET &lt;params&gt;|
|CRUD Delete|Contains a loop for deleting one row from a table containing 100 rows which were populated uing the t-SQL in CRUD Insert. Each iteration prepares and executes a statement. Measurement starts immediately before preparing the 1st DELETE statement and ends immediately after the 100th statement executes.|1000|100|DELETE TOP(1) FROM &lt;tableName&gt;|
|Large Read|Fetches one row at a time from a large prepopulated database until the whole result set is fetched. Measurement starts immediately before preparing the SELECT statement and ends immediately after the last fetch.|10,000,000|1|SELECT \* FROM &lt;tableName&gt;|
|Select @Version|Fetches the SQL Server version. Measurement starts immediately before executing the SELECT statement and ends immediately after fetch.|10,000|1|SELECT @@Version|N/A|
|Create database, table, procedure|Executes t-SQL statements to create a database, table, and procedure. Measurement starts immediately before executing the CREATE DATABASE statement and ends immediately after the DROP DATABASE statement.|1000|1|<ul><li>CREATE DATABASE &lt;dbName&gt;</li><li>USE &lt;dbName&gt;</li><li>CREATE TABLE &lt;tableName&gt; (&lt;params&gt;)</li><li>CREATE PROCEDURE &lt;procName&gt; @id INTEGER, @name VARCHAR(32) AS SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name</li><li>USE MASTER; DROP DATABASE &lt;dbName&gt;</li></ul>|<ul><li>INT</li><li>VARCHAR(32)</li><li>INT</li><li>DATE</li><li>TIMESTAMP</li><li>TIME(7)</li></ul>|
|Connection|Connects and disconnects from the database, with and without connection pooling. Measurement starts immediately before connecting and ends immediately after disconnecting.|1000|1||N/A|

View file

@ -1,3 +0,0 @@
{
"bootstrap": "vendor/autoload.php"
}

View file

@ -1,24 +0,0 @@
--The script can be run to read the results. You can filter out the results ran on a certain date by changing the date at the end.
DECLARE @convByteToMegabyte int = 1048576
select t1.ResultId, Test, Client, Server, Driver, Duration, Memory, Success, Team, StartTime from
(
select pr.ResultId, pr.Success, pt.TestName as Test, cl.HostName as Client, srv.HostName as Server,
tm.TeamName as Team, st.value as Driver, bi.value as Duration, CAST(bi2.value AS decimal(10,2))/@convByteToMegabyte as Memory, dt.value as StartTime from
KeyValueTableBigInt bi,
KeyValueTableBigInt bi2,
KeyValueTableString st,
KeyValueTableDate dt,
PerformanceResults pr,
Clients cl,
PerformanceTests pt,
Teams tm,
Servers srv
where bi.name = 'duration' and bi.ResultId = pr.ResultId
and bi2.name = 'memory' and bi2.ResultId = pr.ResultId
and dt.name = 'startTime' and dt.ResultId = pr.ResultId
and st.name = 'driver' and st.ResultId = pr.ResultId
and cl.ClientId = pr.ClientId
and pt.TestId = pr.TestId
and tm.TeamId = pr.TeamId
and srv.ServerId = pr.ServerId
) t1 where StartTime like '%2017-06-23%'

View file

@ -1,796 +0,0 @@
#!/usr/bin/python3
"""
Description: This script intended to run the Performance Tests on Windows, Linux and Mac.
Requirements:
Run setup_env_unix.sh( Linux and Mac ) or setup_env_windows.ps1( Windows ) before invoking this script.
modify lib/connect.php with the credentials to connect to the test database.
"""
import shutil
from shutil import copyfile
import os
import sys
import argparse
import subprocess
import fileinput
import subprocess
from subprocess import call
import xml.etree.ElementTree as ET
import pyodbc
import platform
import re
import datetime
import time
from time import strftime
import hashlib
"""
Paths to current benchmarks. These constants should be modified if there are any changes in folder structure of the project.
"""
sqlsrv_path = "benchmark" + os.sep + "sqlsrv"
pdo_path = "benchmark" + os.sep + "pdo_sqlsrv"
"""
Path to the connect.php file that contains test database credentials. Note that, the benchmarks are run against this database and it is different from Result database.
"""
connect_file = "lib" + os.sep + "connect.php"
connect_file_bak = connect_file + ".bak"
result_file = "lib" + os.sep + "result_db.php"
"""
Global data format used across the script
"""
fmt = "%Y-%m-%d %H:%M:%S.0000000"
def validate_platform( platform_name ):
"""
This module validates the platform name passed in to the script as an argument.
If no match, the script will stop the execution.
Args:
platform_name (str): Platform name to validate
Returns:
N/A
"""
platforms = [
"Windows10"
, "WindowsServer2016"
, "WindowsServer2012"
, "Ubuntu16"
, "RedHat7"
, "SUSE12"
, "Sierra"]
if platform_name not in platforms:
print ( "Platform must be one of the following:" )
print( platforms )
exit( 1 )
class DB( object ):
"""
A class to keep database credentials
Attributes:
server_name (str): The name or the IP address of the server.
database_name (str): The name of the database
username (str): Database username
password (str): Database password for username
"""
def __init__ ( self
, server_name = None
, database_name = None
, username = None
, password = None):
self.server_name = server_name
self.database_name = database_name
self.username = username
self.password = password
class XMLResult( object ):
"""
A class to keep a result set of a benchmark generated by PHPBench as an XML file.
Attributes:
benchmark_name (str): The name or the benchmark.
success (int): 0 or 1. 0 if the benchmark failed to execute, 1 if the execution was successful.
duration (int,optional): In case of success, time taken to run the benchmark.
memory (int, optional): In case of success, memory peak when executing the benchmark.
iterations(int, optional): In case of success, number of iterations the benchmark was run for.
error_message(str, optional): In case of failure, descriptive error message.
"""
def __init__ ( self
, benchmark_name = None
, success = None
, duration = None
, memory = None
, iterations = None
, error_message = None ):
self.benchmark_name = benchmark_name
self.success = success
self.duration = duration
self.memory = memory
self.iterations = iterations
self.error_message = error_message
def get_test_name( name ):
"""
This module maps PHPBench benchmark names to the names that are used accross the teams.
Args:
name (str): Name of the benchmark
Returns:
The mapped name
Raises:
KeyError when the name passed in does not match any of the keys
"""
test_name_dict = {
'SqlsrvConnectionBench': 'connection'
, 'SqlsrvCreateDbTableProcBench': 'create'
, 'SqlsrvCRUDBench': 'crud'
, 'SqlsrvInsertBench': 'crud-create'
, 'SqlsrvFetchBench': 'crud-retrieve'
, 'SqlsrvUpdateBench': 'crud-update'
, 'SqlsrvDeleteBench': 'crud-delete'
, 'SqlsrvFetchLargeBench': 'large'
, 'SqlsrvSelectVersionBench': 'version'
, 'PDOConnectionBench': 'connection'
, 'PDOCreateDbTableProcBench': 'create'
, 'PDOCRUDBench': 'crud'
, 'PDOInsertBench': 'crud-create'
, 'PDOFetchBench': 'crud-retrieve'
, 'PDOUpdateBench': 'crud-update'
, 'PDODeleteBench': 'crud-delete'
, 'PDOFetchLargeBench': 'large'
, 'PDOSelectVersionBench': 'version'
}
return test_name_dict[ name ]
def get_run_command( path_to_tests, dump_file ):
"""
This module returns the command to run the tests
Args:
path_to_tests (str): The folder that contains the tests to be run
dump_file (str): The name of the XML file to output the results
Returns:
The command to run the tests
"""
command = "vendor" + os.sep + "bin" + os.sep + "phpbench run {0} --dump-file={1}"
return command.format( path_to_tests, dump_file )
def get_id( conn, id_field, table, name_field, value ):
"""
This module returns id of an entry when value is a string
Args:
conn (obj): A connection to the result database
id_field (str): The name of the id column
table (str): The name of the table that contains the entry
name_field (str): The name of the field to compare the value against
value (str): The value that its id is requested
Returns:
id of the value if the value exists in the table, None otherwise
"""
query = "SELECT {0} FROM {1} WHERE {2}='{3}'"
cursor = conn.cursor()
cursor.execute( query.format( id_field, table, name_field, value ))
id = cursor.fetchone()
cursor.close()
if id is not None:
return id[0]
return id
def get_id_no_quote( conn, id_field, table, name_field, value ):
"""
This module returns id of an entry when value is not a string.
@TODO This is a hack, could not get binary binding working with pyodbc.
This module should be removed and get_id should use binding parameters.
Args:
conn (obj): A connection to the result database
id_field (str): The name of the id column
table (str): The name of the table that contains the entry
name_field (str): The name of the field to compare the value against
value (bin): The value that its id is requested
Returns:
id of the value if the value exists in the table, None otherwise
"""
query = "SELECT {0} FROM {1} WHERE {2}={3}"
cursor = conn.cursor()
cursor.execute( query.format( id_field, table, name_field, value ))
id = cursor.fetchone()
cursor.close()
if id is not None:
return id[0]
return id
def get_test_database( database_file ):
"""
This module reads test database details from connect.php and stores them into an instance of DB class
Returns:
A DB object that contains database credentials
"""
test_db = DB()
for line in open( database_file ):
if "server" in line:
test_db.server_name = line.split("=")[1].strip()[1:-2]
elif "database" in line:
test_db.database_name = line.split("=")[1].strip()[1:-2]
elif "uid" in line:
test_db.username = line.split("=")[1].strip()[1:-2]
elif "pwd" in line:
test_db.password = line.split("=")[1].strip()[1:-2]
return test_db
def connect( db ):
"""
This module creates a connection to the given database
Args:
db (obj): database object
Returns:
A connection object to the given database
"""
return pyodbc.connect(
driver="{ODBC Driver 13 for SQL Server}"
, host=db.server_name
, database=db.database_name
, user=db.username
, password=db.password
, autocommit = True)
def get_server_version( server ):
"""
This module returns the version of the given server
Args:
server (obj): Server object to connect to
Returns:
The output of @@Version
"""
conn = connect( server )
cursor = conn.cursor()
cursor.execute( "SELECT @@VERSION")
version = cursor.fetchone()[0]
cursor.close()
return version
def get_sha1_file( filename ):
"""
This module generates sha1sum for the given file
Args:
filename (str): Full path to the file
Returns:
sha1sum hash of the file
"""
hash_size = 256
sha1 = hashlib.sha1()
with open( filename, 'rb' ) as f:
while True:
data = f.read( hash_size )
if not data:
break
sha1.update( data )
return "0x" + sha1.hexdigest()
def insert_server_entry( conn, server_name, server_version ):
"""
This module inserts a new entry into Servers table
Args:
conn (obj): Connection object to the Results database
server_name (str): Name of the Test Server that the tests are run against
server_version (str): @@Version of the Test Server
Returns:
N/A
"""
query = "INSERT INTO Servers ( HostName, Version ) VALUES ( '{0}', '{1}' )"
cursor = conn.cursor()
cursor.execute( query.format( server_name, server_version ))
cursor.close()
def insert_client_entry ( conn, name ):
"""
This module inserts a new entry into Clients table
Args:
conn (obj): Connection object to the Results database
name (str): Name of the Client machine that the tests are run on
Returns:
N/A
"""
query = "INSERT INTO Clients ( HostName ) VALUES( '{0}' )"
cursor = conn.cursor()
cursor.execute( query.format( name ))
cursor.close()
def insert_team_entry ( conn, name ):
"""
This module inserts a new entry into Teams table
Args:
conn (obj): Connection object to the Results database
name (str): Team name
Returns:
N/A
"""
query = "INSERT INTO Teams ( TeamName ) VALUES( '{0}' )"
cursor = conn.cursor()
cursor.execute( query.format( name ))
cursor.close()
def insert_test_entry( conn, name ):
"""
This module inserts a new entry into PerformanceTests table
Args:
conn (obj): Connection object to the Results database
name (str): Test name
Returns:
N/A
"""
#TO-DO Remove unnecessary columns from the table and fix the query string. Amd64 and 0 are used to bypass not null
query = "INSERT INTO PerformanceTests ( TestName, Arch, HashVer ) VALUES( '{0}', 'Amd64', 0 )"
cursor = conn.cursor()
cursor.execute( query.format( name ))
cursor.close()
def insert_driver_entry( conn, driver_path, driver_hash ):
"""
This module inserts a new entry into Drivers table
Args:
conn (obj): Connection object to the Results database
name (str): Full path to the driver
driver_hash (bin): sha1sum hash of the driver
Returns:
N/A
"""
file_date = time.strftime( fmt, time.gmtime( os.path.getmtime( driver_path )))
query = "INSERT INTO Drivers ( Arch, FileDate, SHA1, HashVer ) VALUES ( ?, ?, {0}, 1 )"
cursor = conn.cursor()
cursor.execute( query.format(driver_hash), ( get_php_arch(), file_date ))
cursor.close()
def get_server_id( conn, test_db ):
"""
This module retrieves the id of a Server entry. If the given server does not exist in Servers table,
the module inserts it into the Servers table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
test_db (obj): An object that contains Test Server details
Returns:
id of the given server
"""
server_id = get_id( conn, "ServerId", "Servers", "HostName", test_db.server_name )
if server_id is None:
insert_server_entry( conn, test_db.server_name, get_server_version( test_db ))
server_id = get_id( conn, "ServerId", "Servers", "HostName", test_db.server_name )
return server_id
def get_client_id( conn ):
"""
This module retrieves the id of a Client entry. If the given client does not exist in Clients table,
the module inserts it into the Clients table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
Returns:
id of the client
"""
client_name = platform.node()
client_id = get_id( conn, "ClientId", "Clients", "HostName", client_name )
if client_id is None:
insert_client_entry( conn, client_name )
client_id = get_id( conn, "ClientId", "Clients", "HostName", client_name )
return client_id
def get_team_id( conn ):
"""
This module retrieves the id of a Team entry. If the given team name - PHP does not exist in Teams table,
the module inserts it into the Teams table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
Returns:
id of the team
"""
team_name = "PHP"
team_id = get_id( conn, "TeamId", "Teams", "TeamName", team_name)
if team_id is None:
insert_team_entry( conn, team_name )
team_id = get_id( conn, "TeamId", "Teams", "TeamName", team_name)
return team_id
def get_test_id( conn, test_name ):
"""
This module retrieves the id of a Test entry. If the given test does not exists in PerformanceTests table,
the module inserts it into the PerformanceTests table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
test_name (str): The name of the test that the id is requested for
Returns:
id of the test
"""
test_id = get_id( conn, "TestId", "PerformanceTests", "TestName", test_name )
if test_id is None:
insert_test_entry( conn, test_name )
test_id = get_id( conn, "TestId", "PerformanceTests", "TestName", test_name )
return test_id
def get_driver_id( conn, driver_name ):
"""
This module retrieves the id of a Driver entry. If the given driver does not exists in Drivers table,
the module inserts it into the Drivers table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
driver_name (str): The name of the driver that the id is requested for
Returns:
id of the driver
"""
driver_path = get_path_to_driver( driver_name )
driver_hash = get_sha1_file( driver_path )
driver_id = get_id_no_quote( conn, "DriverId", "Drivers", "SHA1", driver_hash )
if driver_id is None:
insert_driver_entry( conn, driver_path, driver_hash )
driver_id = get_id_no_quote( conn, "DriverId", "Drivers", "SHA1", driver_hash )
return driver_id
def insert_result_entry_and_get_id( conn, test_id, client_id, driver_id, server_id, team_id, success ):
"""
This module inserts a new result entry into PerformanceResults table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
test_id (int): The id of the test
client_id (int): The id of the client that the test was run on
driver_id (int): The id of the driver that the test was run against
server_id (int): The id of the server that the test was run against
team_id (int): The id of the team that the test belongs to
success (int): 0 if the test failed, 1 otherwise
Returns:
id of the result
"""
query = "INSERT INTO PerformanceResults( TestId, ClientId, DriverId, ServerId, TeamId, Success ) OUTPUT INSERTED.ResultId VALUES( {0}, {1}, {2}, {3}, {4}, {5} )"
cursor = conn.cursor()
cursor.execute( query.format( test_id, client_id, driver_id, server_id, team_id, success ))
result_id = cursor.fetchone()
cursor.close()
if result_id is not None:
return result_id[0]
return id
def insert_result_key_value( conn, table_name, result_id, key, value ):
"""
This module inserts a new entry into a key-value table.
Args:
conn (obj): Connection object to the Results database
table_name (string): The name of the table. Current possible values: KeyValueTableBigInt, KeyValueTableDate, KeyValueTableString
result_id (int): The result id that is associated with the key-value table
key (str): name of the property
value (int, date, string): The value of the key
Returns:
N/A
"""
query = "INSERT INTO {0} ( ResultId, name, value ) VALUES( ?, ?, ? )"
cursor = conn.cursor()
cursor.execute( query.format( table_name ), ( result_id, key, value ) )
cursor.close()
def get_php_arch():
"""
This module determines the architecture of the default php of the system
Args:
N/A
Returns
x86 or x64
"""
p = subprocess.Popen( "php -r \"echo PHP_INT_SIZE;\"", stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
if out.decode('ascii') == "8":
return "x64"
elif out.decode('ascii') == "4":
return "x86"
def get_php_version():
"""
This module determines the version of the default php of the system
Args:
N/A
Returns:
php version
"""
p = subprocess.Popen( "php -r \"echo phpversion();\"", stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
return out.decode('ascii')
def get_php_thread():
"""
This module determines the thread safety of the default php of the system
Args:
N/A
Returns:
nts or ts
"""
if os.name == 'nt':
command = "php -i | findstr \"Thread\""
else:
command = "php -i | grep 'Thread'"
p = subprocess.Popen( command, stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
if out.decode('ascii').split()[3].strip() == 'disabled':
return "nts"
else:
return "ts"
def get_driver_version( driver_name ):
"""
This module determines the version of the given php driver.
Args:
driver_name (str): Name of the driver. Possible values sqlsrv and pdo_sqlsrv
Returns:
The version of the given driver
"""
command = "php -r \"echo phpversion('{0}');\""
p = subprocess.Popen( command.format( driver_name ), stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
return out.decode('ascii')
def get_msodbcsql_version( test_db ):
"""
This module determines the version of MSODBCSQL using the sqlsrv driver.
Args:
test_db (obj): An object that contains Test Server details
Returns:
MSODBCSQL version
"""
command = "php -r \"echo sqlsrv_client_info( sqlsrv_connect( '{0}', array( 'UID'=>'{1}', 'PWD'=>'{2}')))['DriverVer'];\""
p = subprocess.Popen( command.format( test_db.server_name, test_db.username, test_db.password ), stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
return out.decode('ascii')
def get_path_to_driver( driver_name ):
"""
This module returns the full path to the given php driver
Args:
driver_name (str): Name of the driver. Possible values sqlsrv and pdo_sqlsrv
Returns:
Full path to the given driver
"""
p = subprocess.Popen( "php -r \"echo ini_get('extension_dir');\"", stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
extension_dir = out.decode('ascii')
if os.name == 'nt':
return extension_dir + os.sep + "php_" + driver_name + ".dll"
else:
return extension_dir + os.sep + driver_name + ".so"
def enable_mars():
"""
This module enables MARS by modifying connect.php file
"""
print( "Enabling MARS...")
with fileinput.FileInput( connect_file, inplace=True, backup='.bak') as file:
for line in file:
print( line.replace( "$mars=false;", "$mars=true;" ), end='')
def disable_mars():
"""
This module disables MARS by modifying connect.php file
"""
print( "Disabling MARS...")
os.remove( connect_file )
copyfile( connect_file_bak, connect_file )
def enable_pooling():
"""
This module enables Connection Pooling.
On Windows, this is done by modifying connect.php file.
On Linux and Mac, odbcinst.ini file needs to be modified.
@TO-DO: Currently modifying odbcinst.ini requires root permissions.
Copy the MSODBCSQL to a location which does not require sudo.
"""
print( "Enabling Pooling...")
if os.name == 'nt':
with fileinput.FileInput( connect_file, inplace=True, backup='.bak') as file:
for line in file:
print( line.replace( "$pooling=false;", "$pooling=true;" ), end='')
else:
# Get the location of odbcinst.ini
odbcinst = os.popen( "odbcinst -j" ).read().splitlines()[1].split()[1]
odbcinst_bak = odbcinst + ".bak"
# Create a copy of odbcinst.ini
copyfile( odbcinst, odbcinst_bak )
# Lines to enable Connection pooling
lines_to_append="CPTimeout=5\n[ODBC]\nPooling=Yes\n"
with open( odbcinst, "a" ) as f:
f.write( lines_to_append )
def disable_pooling():
"""
This module disables Connection Pooling.
On Windows, this is done by modifying connect.php file.
On Linux and Mac, odbcinst.ini file needs to be modified.
@TO-DO: Currently modifying odbcinst.ini requires root permissions.
Copy the MSODBCSQL to a location which does not require sudo.
"""
print("Disabling Pooling...")
if os.name == 'nt':
os.remove( connect_file )
copyfile( connect_file_bak, connect_file )
else:
# Get the location of odbcinst.ini
odbcinst = os.popen( "odbcinst -j" ).read().splitlines()[1].split()[1]
odbcinst_bak = odbcinst + ".bak"
os.remove( odbcinst )
copyfile( odbcinst_bak, odbcinst )
os.remove( odbcinst_bak )
def run_tests( php_driver, test_name ):
"""
This module runs the tests using PHPBench
Args:
php_driver (str): Name of the driver to be tested: sqlsrv, pdo_sqlsrv, or both
test_name (str): File name of the test or all
Returns:
N/A
"""
print("Running the tests...")
add_to_path = ''
if test_name != 'all':
add_to_path = os.sep + test_name
if php_driver == 'sqlsrv' or php_driver == 'both':
call( get_run_command( sqlsrv_path + add_to_path, "sqlsrv-results.xml" ), shell=True )
if php_driver == 'pdo_sqlsrv' or php_driver == 'both':
call( get_run_command( pdo_path + add_to_path, "pdo_sqlsrv-results.xml" ), shell=True )
def parse_results( dump_file ):
"""
This module parses the .xml files generated by PHPBench
@TO-DO: Currently only limited detailes are parsed, such as duration and peak memory.
PHPBench reports a lot more information that can be helpful.
Consider looking at the xml files.
Args:
dump_file (str): The name of the XML file to be parsed.
Returns:
An array of XMLResult objects, where each object contains benchmark information, such as duration and memory.
"""
xml_results = []
tree = ET.parse( dump_file )
root = tree.getroot()
# The following lines assume a certain xml structure.
# Get all the benchmarks in a list
benchmarks = root[0].findall( 'benchmark' )
for benchmark in benchmarks:
xml_result = XMLResult()
#Get the benchmark name and remove the leasing backslash
xml_result.benchmark_name = benchmark.get( 'class' )[1:]
errors = benchmark[0][0].find( 'errors' )
# Store the error message and mark the benchmark as failed if something went wrong when running the benchmark.
if( errors is not None ):
xml_result.success = 0
xml_result.error_message = errors[0].text
# If the bechmark was run successfully, parse the results. This is where you would add code to parse more details about the benchmark.
else:
xml_result.success = 1
# convert microseconds to seconds
xml_result.duration = int( round( int( benchmark[0][0].find( 'stats' ).get( 'sum' )) / 1000000 ))
iterations = benchmark[0][0].findall( 'iteration' )
xml_result.iterations = len( iterations )
# Memory peak is an iteration specific, so going through all the iterations and capturing the highest.
# Memory peak is usually the same for all iterations.
memory_peak = 0
for iteration in iterations:
iter_memory_peak = int( iteration.get( 'mem-peak' ))
if iter_memory_peak > memory_peak:
memory_peak = iter_memory_peak
xml_result.memory = memory_peak
xml_results.append( xml_result )
return xml_results
def parse_and_store_results( dump_file, test_db, result_db, platform, driver, start_time, mars, pooling ):
"""
This module parses the given xml file and stores the results into Result Database.
Args:
dump_file (str): Name of the xml file that containst the results from PHPBench
test_db (obj): An object that contains Test Database details
result_db (obj): An object that contains Result Database details
platform (str): The platform name that the tests are run on
driver (str): Name of the driver, sqlsrv or pdo_sqlsrv
start_time (date): Time when the script was run
mars (int): 0 to turn MARS off, 1 otherwise
pooling (int): 0 to turn Connection Pooling off, 1 otherwise
Returns:
N/A
"""
# Check if the xml file actually exist
if not os.path.exists(dump_file):
print(dump_file + " does not exist")
return
# Connect to the Result Database
conn = connect( result_db )
server_id = get_server_id( conn, test_db )
client_id = get_client_id( conn )
team_id = get_team_id( conn )
driver_id = get_driver_id( conn, driver )
php_arch = get_php_arch()
php_thread = get_php_thread()
php_version = get_php_version()
driver_version = get_driver_version( driver )
msodbcsql_version = get_msodbcsql_version( test_db )
cursor = conn.cursor()
#parse the results from xml file
results = parse_results( dump_file )
# Store every result into the Result Database
for result in results:
test_name = get_test_name( result.benchmark_name )
test_id = get_test_id( conn, test_name )
result_id = insert_result_entry_and_get_id( conn, test_id, client_id, driver_id, server_id, team_id, result.success )
if result.success:
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "duration", result.duration )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "memory", result.memory )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "iterations", result.iterations)
else:
insert_result_key_value( conn, "KeyValueTableString", result_id, "error", result.error_message )
insert_result_key_value( conn, "KeyValueTableDate" , result_id, "startTime" , start_time )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "mars" , mars )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "pooling" , pooling )
insert_result_key_value( conn, "KeyValueTableString", result_id, "driver" , driver )
insert_result_key_value( conn, "KeyValueTableString", result_id, "php_arch" , php_arch )
insert_result_key_value( conn, "KeyValueTableString", result_id, "os" , platform )
insert_result_key_value( conn, "KeyValueTableString", result_id, "php_thread" , php_thread )
insert_result_key_value( conn, "KeyValueTableString", result_id, "php_version" , php_version )
insert_result_key_value( conn, "KeyValueTableString", result_id, "msodbcsql" , msodbcsql_version )
insert_result_key_value( conn, "KeyValueTableString", result_id, "driver_version" , driver_version )
def parse_and_store_results_all( test_db, result_db, platform, start_time, mars, pooling ):
"""
This module parses the given sqlsrv-regular.xml, sqlsrv-large.xml,pdo_sqlsrv-regular.xml, pdo_sqlsrv-large.xml and stores the results into Result Database.
Args:
test_db (obj): An object that contains Test Database details
result_db (obj): An object that contains Result Database details
platform (str): The platform name that the tests are run on
start_time (date): Time when the script was run
mars (int): 0 to turn MARS off, 1 otherwise
pooling (int): 0 to turn Connection Pooling off, 1 otherwise
Returns:
N/A
"""
print("Parsing and storing the results...")
parse_and_store_results( "sqlsrv-results.xml", test_db, result_db, platform, "sqlsrv", start_time, mars, pooling )
parse_and_store_results( "pdo_sqlsrv-results.xml", test_db, result_db, platform, "pdo_sqlsrv", start_time, mars, pooling )
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument( '-platform', '--PLATFORM', required=True, help='The name of the platform the tests run on' )
parser.add_argument( '-php-driver', '--PHP_DRIVER', default='both', help='Name of the PHP driver: sqlsrv, pdo_sqlsrv or both')
parser.add_argument( '-testname', '--TESTNAME', default='all', help='File name for only one test or all' )
args = parser.parse_args()
# Start time is recorded only in the beginning of this script execution. So it is not benchmark specific.
# Start time can be used to group the results
start_time = datetime.datetime.now().strftime( fmt )
print( "Start time: " + start_time )
validate_platform( args.PLATFORM )
result_db = get_test_database( result_file )
test_db = get_test_database( connect_file )
print("Running the tests with default settings...")
run_tests( args.PHP_DRIVER, args.TESTNAME )
parse_and_store_results_all( test_db, result_db, args.PLATFORM, start_time, 0, 0 )
"""
The following lines are commented out, because it already takes a long time to run the tests with the default settings.
Echo block can be uncommented and run separately.
print("Running the tests with MARS ON...")
enable_mars()
run_tests( args.PHP_DRIVER, args.TESTNAME )
parse_and_store_results_all( test_db, result_db, args.PLATFORM, start_time, 1, 0 )
disable_mars()
print("Running the tests with Pooling ON...")
enable_pooling()
run_tests( args.PHP_DRIVER, args.TESTNAME )
parse_and_store_results_all( test_db, result_db, args.PLATFORM, start_time, 0, 1 )
disable_pooling()
"""
exit()

Some files were not shown because too many files have changed in this diff Show more