New upstream version 5.10.1

This commit is contained in:
David Prévot 2023-12-06 16:06:28 +01:00
parent 16459fab38
commit c01a74b734
977 changed files with 122 additions and 115796 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

122
package.xml Normal file
View file

@ -0,0 +1,122 @@
<?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>pdo_sqlsrv</name>
<channel>pecl.php.net</channel>
<summary>Microsoft Drivers for PHP for SQL Server (PDO_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 PDO_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:44</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="12c51672bd54784a06c52f3efc7cf85f" name="CREDITS" role="doc" />
<file md5sum="5367e8bd4b5c4f8034578525fbaff449" name="LICENSE" role="doc" />
<file md5sum="9c0ebc090525b9a42fd8d497415a9893" name="config.m4" role="src" />
<file md5sum="a2410683e00f94a4a56554dd596dc69e" name="config.w32" role="src" />
<file md5sum="a3218c2fed8368a03ba5cda56733ff6d" name="pdo_dbh.cpp" role="src" />
<file md5sum="7e592616e1b465577a1c710ee33efbf1" name="pdo_init.cpp" role="src" />
<file md5sum="d3b524dd16b30016bc09f847540a2a36" name="pdo_parser.cpp" role="src" />
<file md5sum="22d64f72caa89b5b358ae112bfba3e38" name="pdo_stmt.cpp" role="src" />
<file md5sum="91713fff2e691893aa6735dc06de1067" name="pdo_util.cpp" role="src" />
<file md5sum="5aeee2085203a055a107faf2705c2b4f" name="php_pdo_sqlsrv.h" role="src" />
<file md5sum="33f4dd39a0b1f1d0992538d48bdae3fa" name="php_pdo_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="e4bb2a59e60d0f60c48860bf37c083e2" name="template.rc" 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>pdo_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

View file

@ -1,81 +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_ENABLE(sqlsrv, whether to enable sqlsrv functions,
[ --disable-sqlsrv Disable sqlsrv functions], yes)
if test "$PHP_SQLSRV" != "no"; then
sqlsrv_src_class="\
conn.cpp \
util.cpp \
init.cpp \
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 SQLSRV headers])
if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h && test "$PHP_PDO_SQLSRV" != "no" && test "$PHP_PDO_SQLSRV_SHARED" == "no"; then
pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/
shared_src_class=""
elif test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then
sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/
elif test -f $srcdir/shared/core_sqlsrv.h; then
sqlsrv_inc_path=$srcdir/shared/
else
AC_MSG_ERROR([Cannot find SQLSRV headers])
fi
AC_MSG_RESULT($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
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
else
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-z,now"
IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'`
IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='`
if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then
AC_DEFINE(__MUSL__, 1, [ ])
fi
fi
PHP_REQUIRE_CXX()
PHP_ADD_LIBRARY(stdc++, 1, SQLSRV_SHARED_LIBADD)
PHP_ADD_LIBRARY(odbc, 1, SQLSRV_SHARED_LIBADD)
PHP_ADD_LIBRARY(odbcinst, 1, SQLSRV_SHARED_LIBADD)
PHP_SUBST(SQLSRV_SHARED_LIBADD)
AC_DEFINE(HAVE_SQLSRV, 1, [ ])
PHP_ADD_INCLUDE([$sqlsrv_inc_path])
PHP_NEW_EXTENSION(sqlsrv, $sqlsrv_src_class $shared_src_class, $ext_shared,,-std=c++11)
PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1)
fi

View file

@ -1,55 +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_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no");
if( PHP_SQLSRV != "no" ) {
sqlsrv_src_class = " conn.cpp init.cpp stmt.cpp util.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", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") &&
CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&&
CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) {
if (PHP_SQLSRV_SHARED || PHP_PDO_SQLSRV == "no") {
ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" );
}
CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC");
CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_SQLSRV_ODBC");
ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf" );
ADD_FLAG( "CFLAGS_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" );
ADD_FLAG( "CFLAGS_SQLSRV", "/EHsc" );
ADD_FLAG( "CFLAGS_SQLSRV", "/GS" );
ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" );
if (VCVERS >= 1913) {
ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload");
ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre");
} else if (VCVERS == 1900) {
var subver1900 = probe_binary(PHP_CL).substr(6);
if (subver1900 >= 24241) {
ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload");
ADD_FLAG('CFLAGS_SQLSRV', "/Qspectre");
}
}
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" );
EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
} else {
WARNING("sqlsrv not enabled; libraries and headers not found");
}
}

View file

@ -1,1558 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: conn.cpp
//
// Contents: Routines that use connection handles
//
// 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_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
#include <string>
#include <sstream>
// *** internal variables and constants ***
namespace {
const int MAX_CONN_VALSTRING_LEN = 256;
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_CONN;
struct date_as_string_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>( conn );
ss_conn->date_as_string = zend_is_true(value);
}
};
struct format_decimals_func
{
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/)
{
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
ss_conn->format_decimals = zend_is_true(value);
}
};
struct decimal_places_func
{
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/)
{
// first check if the input is an integer
if (Z_TYPE_P(value) != IS_LONG) {
THROW_SS_ERROR(conn, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
}
zend_long decimal_places = Z_LVAL_P(value);
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
decimal_places = NO_CHANGE_DECIMAL_PLACES;
}
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
ss_conn->decimal_places = static_cast<short>(decimal_places);
}
};
struct srv_encrypt_set_func {
static void func(connection_option const* option, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& conn_str)
{
std::string attr;
if (Z_TYPE_P(value_z) == IS_LONG) {
long val = Z_LVAL_P(value_z);
if (val == 1) {
attr = "yes";
} else if (val == 0) {
attr = "no";
} else {
attr = std::to_string(val);
}
} else if (Z_TYPE_P(value_z) == IS_TRUE || Z_TYPE_P(value_z) == IS_FALSE) {
attr = zend_is_true(value_z) ? "yes" : "no";
} else {
attr = Z_STRVAL_P(value_z);
}
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%s};", option->odbc_name, attr.c_str());
conn_str += temp_str;
}
};
struct conn_char_set_func {
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
convert_to_string( value );
const char* encoding = Z_STRVAL_P( value );
size_t encoding_len = Z_STRLEN_P( value );
zend_ulong index = -1;
zend_string* key = NULL;
void* ss_encoding_temp = NULL;
ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) {
sqlsrv_encoding* ss_encoding = reinterpret_cast<sqlsrv_encoding*>( ss_encoding_temp );
ss_encoding_temp = NULL;
if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) {
if ( ss_encoding->not_for_connection ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
}
conn->set_encoding( static_cast<SQLSRV_ENCODING>(ss_encoding->code_page ));
return;
}
} ZEND_HASH_FOREACH_END();
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
}
};
struct bool_conn_str_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%s};", option->odbc_name, (zend_is_true(value) ? "yes" : "no"));
conn_str += temp_str;
}
};
struct 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_LONG, "An integer is expected for this keyword" )
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%ld};", option->odbc_name, Z_LVAL_P(value));
conn_str += temp_str;
}
};
template <unsigned int Attr>
struct int_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( value )), SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
};
template <unsigned int Attr>
struct bool_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)), SQL_IS_UINTEGER);
}
catch( core::CoreException& ) {
throw;
}
}
};
//// *** internal functions ***
void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn );
void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd,
_Inout_ HashTable* ss_conn_options_ht );
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht );
void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
_Inout_ HashTable* options_ht, _Inout_ zval* data );
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ HashTable* options_ht, _Inout_ zval* data );
int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z );
int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len );
}
// constants for parameters used by process_params function(s)
int ss_sqlsrv_conn::descriptor;
const char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn";
// connection specific parameter proccessing. Use the generic function specialised to return a connection
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
rsrc = process_params<ss_sqlsrv_conn>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ##__VA_ARGS__ );\
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
namespace SSStmtOptionNames {
const char QUERY_TIMEOUT[]= "QueryTimeout";
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
const char SCROLLABLE[] = "Scrollable";
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
const char DATE_AS_STRING[] = "ReturnDatesAsStrings";
const char FORMAT_DECIMALS[] = "FormatDecimals";
const char DECIMAL_PLACES[] = "DecimalPlaces";
const char DATA_CLASSIFICATION[] = "DataClassification";
}
namespace SSConnOptionNames {
// most of these strings are the same for both the sqlsrv_connect connection option
// and the name put into the connection string. MARS is the only one that's different.
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char CharacterSet[] = "CharacterSet";
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 DecimalPlaces[] = "DecimalPlaces";
const char FormatDecimals[] = "FormatDecimals";
const char DateAsString[] = "ReturnDatesAsStrings";
const char Driver[] = "Driver";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
const char KeyStoreSecret[] = "KeyStoreSecret";
const char LoginTimeout[] = "LoginTimeout";
const char MARS_Option[] = "MultipleActiveResultSets";
const char MultiSubnetFailover[] = "MultiSubnetFailover";
const char PWD[] = "PWD";
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 UID[] = "UID";
const char WSID[] = "WSID";
const char ComputePool[] = "ComputePool";
const char HostNameInCertificate[] = "HostNameInCertificate";
}
enum SS_CONN_OPTIONS {
SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
SS_CONN_OPTION_FORMAT_DECIMALS,
SS_CONN_OPTION_DECIMAL_PLACES,
};
//List of all statement options supported by this driver
const stmt_option SS_STMT_OPTS[] = {
{
SSStmtOptionNames::QUERY_TIMEOUT,
sizeof( SSStmtOptionNames::QUERY_TIMEOUT ),
SQLSRV_STMT_OPTION_QUERY_TIMEOUT,
std::unique_ptr<stmt_option_query_timeout>( new stmt_option_query_timeout )
},
{
SSStmtOptionNames::SEND_STREAMS_AT_EXEC,
sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ),
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
std::unique_ptr<stmt_option_send_at_exec>( new stmt_option_send_at_exec )
},
{
SSStmtOptionNames::SCROLLABLE,
sizeof( SSStmtOptionNames::SCROLLABLE ),
SQLSRV_STMT_OPTION_SCROLLABLE,
std::unique_ptr<stmt_option_ss_scrollable>( new stmt_option_ss_scrollable )
},
{
SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE,
sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ),
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit )
},
{
SSStmtOptionNames::DATE_AS_STRING,
sizeof( SSStmtOptionNames::DATE_AS_STRING ),
SQLSRV_STMT_OPTION_DATE_AS_STRING,
std::unique_ptr<stmt_option_date_as_string>( new stmt_option_date_as_string )
},
{
SSStmtOptionNames::FORMAT_DECIMALS,
sizeof( SSStmtOptionNames::FORMAT_DECIMALS ),
SQLSRV_STMT_OPTION_FORMAT_DECIMALS,
std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals )
},
{
SSStmtOptionNames::DECIMAL_PLACES,
sizeof( SSStmtOptionNames::DECIMAL_PLACES),
SQLSRV_STMT_OPTION_DECIMAL_PLACES,
std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places )
},
{
SSStmtOptionNames::DATA_CLASSIFICATION,
sizeof( SSStmtOptionNames::DATA_CLASSIFICATION ),
SQLSRV_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>{} },
};
// List of all connection options supported by this driver.
const connection_option SS_CONN_OPTS[] = {
{
SSConnOptionNames::APP,
sizeof( SSConnOptionNames::APP ),
SQLSRV_CONN_OPTION_APP,
ODBCConnOptions::APP,
sizeof( ODBCConnOptions::APP ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::AccessToken,
sizeof( SSConnOptionNames::AccessToken ),
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
ODBCConnOptions::AccessToken,
sizeof( ODBCConnOptions::AccessToken),
CONN_ATTR_STRING,
access_token_set_func::func
},
{
SSConnOptionNames::ApplicationIntent,
sizeof( SSConnOptionNames::ApplicationIntent ),
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
ODBCConnOptions::ApplicationIntent,
sizeof( ODBCConnOptions::ApplicationIntent ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::AttachDBFileName,
sizeof( SSConnOptionNames::AttachDBFileName ),
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
ODBCConnOptions::AttachDBFileName,
sizeof( ODBCConnOptions::AttachDBFileName ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::CharacterSet,
sizeof( SSConnOptionNames::CharacterSet ),
SQLSRV_CONN_OPTION_CHARACTERSET,
ODBCConnOptions::CharacterSet,
sizeof( ODBCConnOptions::CharacterSet ),
CONN_ATTR_STRING,
conn_char_set_func::func
},
{
SSConnOptionNames::Authentication,
sizeof( SSConnOptionNames::Authentication ),
SQLSRV_CONN_OPTION_AUTHENTICATION,
ODBCConnOptions::Authentication,
sizeof( ODBCConnOptions::Authentication ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::ConnectionPooling,
sizeof(SSConnOptionNames::ConnectionPooling),
SQLSRV_CONN_OPTION_CONN_POOLING,
ODBCConnOptions::ConnectionPooling,
sizeof(ODBCConnOptions::ConnectionPooling),
CONN_ATTR_BOOL,
conn_null_func::func
},
{
SSConnOptionNames::Language,
sizeof(SSConnOptionNames::Language),
SQLSRV_CONN_OPTION_LANGUAGE,
ODBCConnOptions::Language,
sizeof(ODBCConnOptions::Language),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::Driver,
sizeof(SSConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
SSConnOptionNames::ColumnEncryption,
sizeof(SSConnOptionNames::ColumnEncryption),
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
ODBCConnOptions::ColumnEncryption,
sizeof(ODBCConnOptions::ColumnEncryption),
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
SSConnOptionNames::ConnectRetryCount,
sizeof( SSConnOptionNames::ConnectRetryCount ),
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
ODBCConnOptions::ConnectRetryCount,
sizeof( ODBCConnOptions::ConnectRetryCount ),
CONN_ATTR_INT,
int_conn_str_func::func
},
{
SSConnOptionNames::ConnectRetryInterval,
sizeof( SSConnOptionNames::ConnectRetryInterval ),
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
ODBCConnOptions::ConnectRetryInterval,
sizeof( ODBCConnOptions::ConnectRetryInterval ),
CONN_ATTR_INT,
int_conn_str_func::func
},
{
SSConnOptionNames::Database,
sizeof( SSConnOptionNames::Database ),
SQLSRV_CONN_OPTION_DATABASE,
ODBCConnOptions::Database,
sizeof( ODBCConnOptions::Database ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::Encrypt,
sizeof( SSConnOptionNames::Encrypt ),
SQLSRV_CONN_OPTION_ENCRYPT,
ODBCConnOptions::Encrypt,
sizeof( ODBCConnOptions::Encrypt ),
CONN_ATTR_MIXED,
srv_encrypt_set_func::func
},
{
SSConnOptionNames::Failover_Partner,
sizeof( SSConnOptionNames::Failover_Partner ),
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
ODBCConnOptions::Failover_Partner,
sizeof( ODBCConnOptions::Failover_Partner ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::KeyStoreAuthentication,
sizeof( SSConnOptionNames::KeyStoreAuthentication ),
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
ODBCConnOptions::KeyStoreAuthentication,
sizeof( ODBCConnOptions::KeyStoreAuthentication ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::KeyStorePrincipalId,
sizeof( SSConnOptionNames::KeyStorePrincipalId ),
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
ODBCConnOptions::KeyStorePrincipalId,
sizeof( ODBCConnOptions::KeyStorePrincipalId ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::KeyStoreSecret,
sizeof( SSConnOptionNames::KeyStoreSecret ),
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
ODBCConnOptions::KeyStoreSecret,
sizeof( ODBCConnOptions::KeyStoreSecret ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::LoginTimeout,
sizeof( SSConnOptionNames::LoginTimeout ),
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
ODBCConnOptions::LoginTimeout,
sizeof( ODBCConnOptions::LoginTimeout ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
},
{
SSConnOptionNames::MARS_Option,
sizeof( SSConnOptionNames::MARS_Option ),
SQLSRV_CONN_OPTION_MARS,
ODBCConnOptions::MARS_ODBC,
sizeof( ODBCConnOptions::MARS_ODBC ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::MultiSubnetFailover,
sizeof( SSConnOptionNames::MultiSubnetFailover ),
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
ODBCConnOptions::MultiSubnetFailover,
sizeof( ODBCConnOptions::MultiSubnetFailover ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::QuotedId,
sizeof( SSConnOptionNames::QuotedId ),
SQLSRV_CONN_OPTION_QUOTED_ID,
ODBCConnOptions::QuotedId,
sizeof( ODBCConnOptions::QuotedId ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::TraceFile,
sizeof( SSConnOptionNames::TraceFile ),
SQLSRV_CONN_OPTION_TRACE_FILE,
ODBCConnOptions::TraceFile,
sizeof( ODBCConnOptions::TraceFile ),
CONN_ATTR_STRING,
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
},
{
SSConnOptionNames::TraceOn,
sizeof( SSConnOptionNames::TraceOn ),
SQLSRV_CONN_OPTION_TRACE_ON,
ODBCConnOptions::TraceOn,
sizeof( ODBCConnOptions::TraceOn ),
CONN_ATTR_BOOL,
bool_conn_attr_func<SQL_ATTR_TRACE>::func
},
{
SSConnOptionNames::TransactionIsolation,
sizeof( SSConnOptionNames::TransactionIsolation ),
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
ODBCConnOptions::TransactionIsolation,
sizeof( ODBCConnOptions::TransactionIsolation ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_COPT_SS_TXN_ISOLATION>::func
},
{
SSConnOptionNames::TrustServerCertificate,
sizeof( SSConnOptionNames::TrustServerCertificate ),
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
ODBCConnOptions::TrustServerCertificate,
sizeof( ODBCConnOptions::TrustServerCertificate ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::TransparentNetworkIPResolution,
sizeof(SSConnOptionNames::TransparentNetworkIPResolution),
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
ODBCConnOptions::TransparentNetworkIPResolution,
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::WSID,
sizeof( SSConnOptionNames::WSID ),
SQLSRV_CONN_OPTION_WSID,
ODBCConnOptions::WSID,
sizeof( ODBCConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::DateAsString,
sizeof( SSConnOptionNames::DateAsString ),
SS_CONN_OPTION_DATE_AS_STRING,
SSConnOptionNames::DateAsString,
sizeof( SSConnOptionNames::DateAsString ),
CONN_ATTR_BOOL,
date_as_string_func::func
},
{
SSConnOptionNames::FormatDecimals,
sizeof( SSConnOptionNames::FormatDecimals),
SS_CONN_OPTION_FORMAT_DECIMALS,
SSConnOptionNames::FormatDecimals,
sizeof( SSConnOptionNames::FormatDecimals),
CONN_ATTR_BOOL,
format_decimals_func::func
},
{
SSConnOptionNames::DecimalPlaces,
sizeof( SSConnOptionNames::DecimalPlaces),
SS_CONN_OPTION_DECIMAL_PLACES,
SSConnOptionNames::DecimalPlaces,
sizeof( SSConnOptionNames::DecimalPlaces),
CONN_ATTR_INT,
decimal_places_func::func
},
{
SSConnOptionNames::ComputePool,
sizeof(SSConnOptionNames::ComputePool),
SQLSRV_CONN_OPTION_COMPUTE_POOL,
ODBCConnOptions::ComputePool,
sizeof(ODBCConnOptions::ComputePool),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::HostNameInCertificate,
sizeof(SSConnOptionNames::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
};
// sqlsrv_connect( string $serverName [, array $connectionInfo])
//
// Creates a connection resource and opens a connection. By default, the
// connection is attempted using Windows Authentication.
//
// Parameters
// $serverName: A string specifying the name of the server to which a connection
// is being established. An instance name (for example, "myServer\instanceName")
// or port number (for example, "myServer, 1521") can be included as part of
// this string. For a complete description of the options available for this
// parameter, see the Server keyword in the ODBC Driver Connection String
// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server.
//
// $connectionInfo [OPTIONAL]: An associative array that contains connection
// attributes (for example, array("Database" => "AdventureWorks")).
//
// Return Value
// A PHP connection resource. If a connection cannot be successfully created and
// opened, false is returned
PHP_FUNCTION ( sqlsrv_connect )
{
LOG_FUNCTION( "sqlsrv_connect" );
g_ss_henv_cp->set_func(_FN_);
g_ss_henv_ncp->set_func(_FN_);
reset_errors();
const char* server = NULL;
zval* options_z = NULL;
char* uid = NULL;
char* pwd = NULL;
size_t server_len = 0;
zval conn_z;
ZVAL_UNDEF(&conn_z);
// get the server name and connection options
int result = zend_parse_parameters( ZEND_NUM_ARGS(), "s|a", &server, &server_len, &options_z );
CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_ss_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) {
RETURN_FALSE;
}
hash_auto_ptr ss_conn_options_ht;
hash_auto_ptr stmts;
ss_sqlsrv_conn* conn = NULL;
try {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_conn_options_ht );
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, ss_conn_options_ht, 10 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
// Either of g_ss_henv_cp or g_ss_henv_ncp can be used to propagate the error.
::validate_conn_options( *g_ss_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht );
// call the core connect function
conn = static_cast<ss_sqlsrv_conn*>( core_sqlsrv_connect( *g_ss_henv_cp, *g_ss_henv_ncp, &core::allocate_conn<ss_sqlsrv_conn>,
server, uid, pwd, ss_conn_options_ht, ss_error_handler,
SS_CONN_OPTS, NULL, "sqlsrv_connect" ));
SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." );
// create a bunch of statements
ALLOC_HASHTABLE( stmts );
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ );
// register the connection with the PHP runtime
ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name);
conn->stmts = stmts;
stmts.transferred();
RETURN_RES( Z_RES(conn_z) );
}
catch( core::CoreException& ) {
if( conn != NULL ) {
conn->invalidate();
}
RETURN_FALSE;
}
catch( ... ) {
DIE("sqlsrv_connect: Unknown exception caught.");
}
}
// sqlsrv_begin_transaction( resource $conn )
//
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback
// or sqlsrv_commit.
//
// The SQLSRV driver is in auto-commit mode by default. This means that all
// queries are automatically committed upon success unless they have been
// designated as part of an explicit transaction by using
// sqlsrv_begin_transaction.
//
// If sqlsrv_begin_transaction is called after a transaction has already been
// initiated on the connection but not completed by calling either sqlsrv_commit
// or sqlsrv_rollback, the call returns false and an Already in Transaction
// error is added to the error collection.
//
// Parameters
// $conn: The connection with which the transaction is associated.
//
// Return Value
// A Boolean value: true if the transaction was successfully begun. Otherwise, false.
PHP_FUNCTION( sqlsrv_begin_transaction )
{
LOG_FUNCTION( "sqlsrv_begin_transaction" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if already in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) {
RETURN_FALSE;
}
try {
core_sqlsrv_begin_transaction( conn );
conn->in_transaction = true;
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE("sqlsrv_begin_transaction: Unknown exception caught.");
}
}
// sqlsrv_close( resource $conn )
// Closes the specified connection and releases associated resources.
//
// Parameters
// $conn: The connection to be closed. Null is a valid value parameter for this
// parameter. This allows the function to be called multiple times in a
// script. For example, if you close a connection in an error condition and
// close it again at the end of the script, the second call to sqlsrv_close will
// return true because the first call to sqlsrv_close (in the error condition)
// sets the connection resource to null.
//
// Return Value
// The Boolean value true unless the function is called with an invalid
// parameter. If the function is called with an invalid parameter, false is
// returned.
PHP_FUNCTION( sqlsrv_close )
{
LOG_FUNCTION( "sqlsrv_close" );
zval* conn_r = NULL;
ss_sqlsrv_conn* conn = NULL;
sqlsrv_context_auto_ptr error_ctx;
reset_errors();
try {
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
if( zend_parse_parameters(ZEND_NUM_ARGS(), "r", &conn_r) == FAILURE ) {
// Check if it was a zval
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &conn_r );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
// if sqlsrv_close was called on a non-existent connection then we just return success.
if( Z_TYPE_P( conn_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
SQLSRV_ASSERT( conn_r != NULL, "sqlsrv_close: conn_r was null" );
conn = static_cast<ss_sqlsrv_conn*>( zend_fetch_resource( Z_RES_P( conn_r ), ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor ));
// if sqlsrv_close was called on an already closed connection then we just return success.
if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) {
RETURN_TRUE;
}
CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
conn->set_func(_FN_);
// cause any variables still holding a reference to this to be invalid so they cause
// an error when passed to a sqlsrv function. There's nothing we can do if the
// removal fails, so we just log it and move on.
#if PHP_VERSION_ID < 80000
if (zend_list_close(Z_RES_P(conn_r)) == FAILURE) {
LOG(SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P(conn_r));
}
#else
zend_list_close(Z_RES_P(conn_r));
#endif
// when conn_r is first parsed in zend_parse_parameters, conn_r becomes a zval that points to a zend_resource with a refcount of 2
// need to DELREF here so the refcount becomes 1 and conn_r can be appropriate destroyed by the garbage collector when it goes out of scope
// zend_list_close only destroy the resource pointed to by Z_RES_P( conn_r ), not the zend_resource itself
Z_TRY_DELREF_P(conn_r);
ZVAL_NULL( conn_r );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_close: Unknown exception caught." );
}
}
void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc )
{
// Without sqlsrv_close(), this function is invoked by php during the final clean up stage.
// To prevent memory/resource leaks, no more logging at this point.
//LOG_FUNCTION( "sqlsrv_conn_dtor" );
// get the structure
ss_sqlsrv_conn *conn = static_cast<ss_sqlsrv_conn*>( rsrc->ptr );
SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null");
conn->set_func(__func__);
// close all statements associated with the connection.
sqlsrv_conn_close_stmts( conn );
// close the connection itself.
core_sqlsrv_close( conn );
rsrc->ptr = NULL;
}
// sqlsrv_commit( resource $conn )
//
// Commits the current transaction on the specified connection and returns the
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
// sqlsrv_commit.
// The SQLSRV driver is in auto-commit mode by
// default. This means that all queries are automatically committed upon success
// unless they have been designated as part of an explicit transaction by using
// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is
// not in an active transaction and that was initiated with
// sqlsrv_begin_transaction, the call returns false and a Not in Transaction
// error is added to the error collection.
//
// Parameters
// $conn: The connection on which the transaction is active.
//
// Return Value
// A Boolean value: true if the transaction was successfully committed. Otherwise, false.
PHP_FUNCTION( sqlsrv_commit )
{
LOG_FUNCTION( "sqlsrv_commit" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if not in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
RETURN_FALSE;
}
try {
conn->in_transaction = false;
core_sqlsrv_commit( conn );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_commit: Unknown exception caught." );
}
}
// sqlsrv_rollback( resource $conn )
//
// Rolls back the current transaction on the specified connection and returns
// the connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
// sqlsrv_commit.
//
// The SQLSRV driver is in auto-commit mode by default. This
// means that all queries are automatically committed upon success unless they
// have been designated as part of an explicit transaction by using
// sqlsrv_begin_transaction.
//
// If sqlsrv_rollback is called on a connection that is not in an active
// transaction that was initiated with sqlsrv_begin_transaction, the call
// returns false and a Not in Transaction error is added to the error
// collection.
//
// Parameters
// $conn: The connection on which the transaction is active.
//
// Return Value
// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false.
PHP_FUNCTION( sqlsrv_rollback )
{
LOG_FUNCTION( "sqlsrv_rollback" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if not in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
RETURN_FALSE;
}
try {
conn->in_transaction = false;
core_sqlsrv_rollback( conn );
RETURN_TRUE;
}
catch( core::CoreException& ){
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_rollback: Unknown exception caught." );
}
}
// sqlsrv_client_info
// Returns the ODBC driver's dll name, version and the ODBC version. Also returns
// the version of this extension.
// Parameters:
// $conn - The connection resource by which the client and server are connected.
PHP_FUNCTION( sqlsrv_client_info )
{
LOG_FUNCTION( "sqlsrv_client_info" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
try {
core_sqlsrv_get_client_info(conn, return_value);
// Add the 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]);
} catch (core::CoreException&) {
RETURN_FALSE;
} catch (...) {
DIE("sqlsrv_client_info: Unknown exception caught.");
}
}
// sqlsrv_server_info( resource $conn )
//
// Returns information about the server.
//
// Parameters
// $conn: The connection resource by which the client and server are connected.
//
// Return Value
// An associative array with the following keys:
// CurrentDatabase
// The database currently being targeted.
// SQLServerVersion
// The version of SQL Server.
// SQLServerName
// The name of the server.
PHP_FUNCTION( sqlsrv_server_info )
{
try {
LOG_FUNCTION("sqlsrv_server_info");
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS(conn, "r", _FN_, 0);
core_sqlsrv_get_server_info(conn, return_value);
} catch (core::CoreException&) {
RETURN_FALSE;
} catch (...) {
DIE("sqlsrv_server_info: Unknown exception caught.");
}
}
// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]])
//
// Creates a statement resource associated with the specified connection. A statement
// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute.
// In between each execution, the values may be updated by changing the value of the
// variables bound. Output parameters cannot be relied upon to contain their results until
// all rows are processed.
//
// Parameters
// $conn: The connection resource associated with the created statement.
//
// $tsql: The Transact-SQL expression that corresponds to the created statement.
//
// $params [OPTIONAL]: An array of values that correspond to parameters in a
// parameterized query. Each parameter may be specified as:
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
// When given just a $value, the direction is default input, and phptype is the value
// given, with the sql type inferred from the php type.
//
// $options [OPTIONAL]: An associative array that sets query properties. The
// table below lists the supported keys and corresponding values:
// QueryTimeout
// Sets the query timeout in seconds. By default, the driver will wait
// indefinitely for results.
// SendStreamParamsAtExec
// Configures the driver to send all stream data at execution (true), or to
// send stream data in chunks (false). By default, the value is set to
// true. For more information, see sqlsrv_send_stream_data.
//
// Return Value
// A statement resource. If the statement resource cannot be created, false is returned.
PHP_FUNCTION( sqlsrv_prepare )
{
LOG_FUNCTION( "sqlsrv_prepare" );
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
ss_sqlsrv_conn* conn = NULL;
char *sql = NULL;
zend_long sql_len = 0;
zval* params_z = NULL;
zval* options_z = NULL;
hash_auto_ptr ss_stmt_options_ht;
zval stmt_z;
ZVAL_UNDEF(&stmt_z);
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, &params_z, &options_z );
try {
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_stmt_options_ht );
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
validate_stmt_options( *conn, options_z, ss_stmt_options_ht );
}
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( sql == NULL ) {
DIE( "sqlsrv_prepare: sql string was null." );
}
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
ss_stmt_options_ht, SS_STMT_OPTS,
ss_error_handler, NULL ) );
core_sqlsrv_prepare( stmt, sql, sql_len );
if (params_z) {
stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval));
ZVAL_COPY(stmt->params_z, params_z);
}
stmt->prepared = true;
// register the statement with the PHP runtime
ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name );
// store the resource id with the connection so the connection
// can release this statement when it closes.
zend_long next_index = zend_hash_next_free_element( conn->stmts );
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z);
stmt->conn_index = next_index;
// the statement is now registered with EG( regular_list )
stmt.transferred();
RETURN_RES(Z_RES(stmt_z));
}
catch( core::CoreException& ) {
if( stmt ) {
stmt->conn = NULL;
stmt->~ss_sqlsrv_stmt();
}
if (!Z_ISUNDEF(stmt_z)) {
free_stmt_resource(&stmt_z);
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_prepare: Unknown exception caught." );
}
}
// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]])
//
// Creates a statement resource associated with the specified connection. The statement
// is immediately executed and may not be executed again using sqlsrv_execute.
//
// Parameters
// $conn: The connection resource associated with the created statement.
//
// $tsql: The Transact-SQL expression that corresponds to the created statement.
//
// $params [OPTIONAL]: An array of values that correspond to parameters in a
// parameterized query. Each parameter may be specified as:
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
// When given just a $value, the direction is default input, and phptype is the value
// given, with the sql type inferred from the php type.
//
// $options [OPTIONAL]: An associative array that sets query properties. The
// table below lists the supported keys and corresponding values:
// QueryTimeout
// Sets the query timeout in seconds. By default, the driver will wait
// indefinitely for results.
// SendStreamParamsAtExec
// Configures the driver to send all stream data at execution (true), or to
// send stream data in chunks (false). By default, the value is set to
// true. For more information, see sqlsrv_send_stream_data.
//
// Return Value
// A statement resource. If the statement resource cannot be created, false is returned.
PHP_FUNCTION( sqlsrv_query )
{
LOG_FUNCTION( "sqlsrv_query" );
ss_sqlsrv_conn* conn = NULL;
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
char* sql = NULL;
hash_auto_ptr ss_stmt_options_ht;
size_t sql_len = 0;
zval* options_z = NULL;
zval* params_z = NULL;
zval stmt_z;
ZVAL_UNDEF(&stmt_z);
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, &params_z, &options_z );
try {
// check for statement options
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_stmt_options_ht );
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR,
0 /*persistent*/ );
validate_stmt_options( *conn, options_z, ss_stmt_options_ht );
}
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( sql == NULL ) {
DIE( "sqlsrv_query: sql string was null." );
}
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
ss_stmt_options_ht, SS_STMT_OPTS,
ss_error_handler, NULL ) );
if( params_z ) {
stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval));
ZVAL_COPY(stmt->params_z, params_z);
}
stmt->set_func( "sqlsrv_query" );
bind_params( stmt );
// execute the statement
core_sqlsrv_execute( stmt, sql, static_cast<int>( sql_len ) );
// register the statement with the PHP runtime
ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name);
// store the resource id with the connection so the connection
// can release this statement when it closes.
zend_ulong next_index = zend_hash_next_free_element( conn->stmts );
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z);
stmt->conn_index = next_index;
stmt.transferred();
RETURN_RES(Z_RES(stmt_z));
}
catch( core::CoreException& ) {
if( stmt ) {
stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself
stmt->~ss_sqlsrv_stmt();
}
if (!Z_ISUNDEF(stmt_z)) {
free_stmt_resource(&stmt_z);
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_query: Unknown exception caught." );
}
}
void free_stmt_resource( _Inout_ zval* stmt_z )
{
#if PHP_VERSION_ID < 80000
if (FAILURE == zend_list_close(Z_RES_P(stmt_z))) {
LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z));
}
#else
zend_list_close(Z_RES_P(stmt_z));
#endif
ZVAL_NULL( stmt_z );
zval_ptr_dtor(stmt_z);
}
// internal connection functions
namespace {
// must close all statement handles opened by this connection before closing the connection
// no errors are returned, since close should always succeed
void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn )
{
//pre-condition check
SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an "
"already destroyed connection.");
SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." );
// loop through the stmts hash table and destroy each stmt resource so we can close the
// ODBC connection
zval* rsrc_ptr = NULL;
ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) {
try {
int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE;
CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) {
throw core::CoreException();
}
}
catch( core::CoreException& ) {
DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" );
}
// see if the statement is still valid, and if not skip to the next one
// presumably this should never happen because if it's in the list, it should still be valid
// by virtue that a statement resource should remove itself from its connection when it is
// destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource
// and move to the next one.
ss_sqlsrv_stmt* stmt = NULL;
stmt = static_cast<ss_sqlsrv_stmt*>( Z_RES_VAL_P( rsrc_ptr ));
if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) {
LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves"
" from the connection so this shouldn't be out of sync." );
continue;
}
// delete the statement by deleting it from Zend's resource list, which will force its destruction
stmt->conn = NULL;
// this would call the destructor on the statement.
// There's nothing we can do if the removal fails, so we just log it and move on.
#if PHP_VERSION_ID < 80000
if (zend_list_close(Z_RES_P(rsrc_ptr)) == FAILURE) {
LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr));
}
#else
zend_list_close(Z_RES_P(rsrc_ptr));
#endif
} ZEND_HASH_FOREACH_END();
zend_hash_destroy( conn->stmts );
FREE_HASHTABLE( conn->stmts );
conn->stmts = NULL;
}
int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z )
{
for( int i=0; SS_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i )
{
if( key_len == SS_CONN_OPTS[i].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[i].sqlsrv_name )) {
switch( SS_CONN_OPTS[i].value_type ) {
case CONN_ATTR_BOOL:
// bool attributes can be either strings to be appended to the connection string
// as yes or no or integral connection attributes. This will have to be reworked
// if we ever introduce a boolean connection option that maps to a string connection
// attribute.
break;
case CONN_ATTR_MIXED:
break;
case CONN_ATTR_INT:
{
CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
SS_CONN_OPTS[i].sqlsrv_name )
{
throw ss::SSException();
}
break;
}
case CONN_ATTR_STRING:
{
CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
SS_CONN_OPTS[i].sqlsrv_name ) {
throw ss::SSException();
}
char* value = Z_STRVAL_P( value_z );
size_t value_len = Z_STRLEN_P( value_z );
bool escaped = core_is_conn_opt_value_escaped( value, value_len );
CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[i].sqlsrv_name ) {
throw ss::SSException();
}
break;
}
case CONN_ATTR_INVALID:
SQLSRV_ASSERT(false, "Should not have reached CONN_ATTR_INVALID.");
break;
}
return SS_CONN_OPTS[i].conn_option_key;
}
}
return SQLSRV_CONN_OPTION_INVALID;
}
int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len )
{
for( int i = 0; SS_STMT_OPTS[i].key != SQLSRV_STMT_OPTION_INVALID; ++i )
{
if( key_len == SS_STMT_OPTS[i].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[i].name )) {
return SS_STMT_OPTS[i].key;
}
}
return SQLSRV_STMT_OPTION_INVALID;
}
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
_Inout_ HashTable* options_ht, _Inout_ zval* data )
{
int option_key = ::get_stmt_option_key( key, key_len );
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) {
throw ss::SSException();
}
Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too.
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data );
}
void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
_Inout_ HashTable* options_ht, _Inout_ zval* data )
{
int option_key = ::get_conn_option_key( ctx, key, key_len, data );
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, ZSTR_VAL( key ) ) {
throw ss::SSException();
}
Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too.
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data );
}
// Iterates through the list of statement options provided by the user and validates them
// against the list of supported statement options by this driver. After validation
// creates a Hashtable of statement options to be sent to the core layer for processing.
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht )
{
try {
if( stmt_options ) {
HashTable* options_ht = Z_ARRVAL_P( stmt_options );
zend_ulong 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;
size_t key_len = 0;
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
if (type != HASH_KEY_IS_STRING) {
CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) {
throw core::CoreException();
}
}
else if ( key != NULL ) {
key_len = ZSTR_LEN( key ) + 1;
add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data );
}
else {
DIE( "validate_stmt_options: key was null." );
}
} ZEND_HASH_FOREACH_END();
}
}
catch( core::CoreException& ) {
throw;
}
}
// Iterates through the list of connection options provided by the user and validates them
// against the predefined list of supported connection options by this driver. After validation
// creates a Hashtable of connection options to be sent to the core layer for processing.
void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd, _Inout_ HashTable* ss_conn_options_ht )
{
try {
if( user_options_z ) {
HashTable* options_ht = Z_ARRVAL_P( user_options_z );
zend_ulong 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(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) {
throw ss::SSException();
}
if ( key != NULL ) {
// Length of the key string does not include the null terminator in PHP7, +1 has to be added
size_t key_len = ZSTR_LEN( key ) + 1;
if ( key_len == sizeof( SSConnOptionNames::UID ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::UID )) {
*uid = Z_STRVAL_P( data );
}
else if ( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) {
*pwd = Z_STRVAL_P( data );
}
else {
::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data );
}
}
else {
DIE( "validate_conn_options: key was null." );
}
} ZEND_HASH_FOREACH_END();
}
}
catch( core::CoreException& ) {
throw;
}
}
} // namespace

View file

@ -1,736 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: init.cpp
// Contents: initialization routines 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
#ifdef COMPILE_DL_SQLSRV
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE();
#endif
ZEND_GET_MODULE(g_sqlsrv)
#endif
extern "C" {
ZEND_DECLARE_MODULE_GLOBALS(sqlsrv);
}
// module global variables (initialized in minit and freed in mshutdown)
HashTable* g_ss_errors_ht = NULL;
// special list of warnings to ignore even if warnings are treated as errors
HashTable* g_ss_warnings_to_ignore_ht = NULL;
// encodings we understand
HashTable* g_ss_encodings_ht = NULL;
// Destructors called by Zend for each element in the hashtable
void sqlsrv_error_const_dtor( _Inout_ zval* element );
void sqlsrv_encoding_dtor( _Inout_ zval* element );
// henv context for creating connections
sqlsrv_context* g_ss_henv_cp;
sqlsrv_context* g_ss_henv_ncp;
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_INIT;
}
// argument info structures for functions, arranged alphabetically.
// see zend_API.h in the PHP sources for more information about these macros
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, setting )
ZEND_ARG_INFO( 0, value )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, server_name )
ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 0, 0 )
ZEND_ARG_INFO( 0, errors_and_or_warnings )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, fetch_type )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, class_name )
ZEND_ARG_INFO( 0, ctor_params )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, setting )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, field_index )
ZEND_ARG_INFO( 0, get_as_type )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, conn )
ZEND_ARG_INFO( 0, tsql )
ZEND_ARG_INFO( 0, params )
ZEND_ARG_INFO( 0, options )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, conn )
ZEND_ARG_INFO( 0, tsql )
ZEND_ARG_INFO( 0, params )
ZEND_ARG_INFO( 0, options )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 )
ZEND_ARG_INFO( 0, size )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 )
ZEND_ARG_INFO( 0, precision )
ZEND_ARG_INFO( 0, scale )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 )
ZEND_ARG_INFO( 0, encoding )
ZEND_END_ARG_INFO()
// function table with associated arginfo structures
zend_function_entry sqlsrv_functions[] = {
PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo )
PHP_FE( sqlsrv_close, sqlsrv_close_arginfo )
PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo )
PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo )
PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo )
PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo )
PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo )
PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo )
PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo )
PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo )
PHP_FE( sqlsrv_query, sqlsrv_query_arginfo )
PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo )
PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo )
PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo )
PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo )
PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo )
PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo )
PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo )
PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo )
PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo )
PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo )
PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo )
PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo )
PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo )
PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo )
PHP_FE( sqlsrv_free_stmt, sqlsrv_free_stmt_arginfo )
PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo )
PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo )
PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo )
{NULL, NULL, NULL} // end of the table
};
// 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_sqlsrv_module_entry =
{
STANDARD_MODULE_HEADER,
"sqlsrv",
sqlsrv_functions, // exported function table
// initialization and shutdown functions
PHP_MINIT(sqlsrv),
PHP_MSHUTDOWN(sqlsrv),
PHP_RINIT(sqlsrv),
PHP_RSHUTDOWN(sqlsrv),
PHP_MINFO(sqlsrv),
// version of the extension. Matches the version resource of the extension dll
VER_FILEVERSION_STR,
PHP_MODULE_GLOBALS(sqlsrv),
NULL,
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
// Module initialization
// This function is called once per execution of the Zend engine
// We use it to:
// 1) Register our constants. See MSDN or the function below for the exact constants
// we register.
// 2) Register our resource types (connection, statement, and stream types)
// 3) Allocate the environment handles for ODBC connections (1 for non pooled
// connections and 1 for pooled connections)
// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries
PHP_MINIT_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( type );
core_sqlsrv_register_severity_checker(ss_severity_check);
// our global variables are initialized in the RINIT function
#if defined(ZTS)
if( ts_allocate_id( &sqlsrv_globals_id,
sizeof( zend_sqlsrv_globals ),
(ts_allocate_ctor) NULL,
(ts_allocate_dtor) NULL ) == 0 )
return FAILURE;
ZEND_TSRMLS_CACHE_UPDATE();
#endif
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ));
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long ));
REGISTER_INI_ENTRIES();
LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set
// register connection resource
ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection",
module_number );
if( ss_sqlsrv_conn::descriptor == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ );
return FAILURE;
}
// register statement resources
ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number );
if( ss_sqlsrv_stmt::descriptor == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ );
return FAILURE;
}
sqlsrv_sqltype constant_type;
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_TABLE", SQLSRV_PHPTYPE_TABLE, CONST_PERSISTENT | CONST_CS);
std::string bin = "binary";
std::string chr = "char";
REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", &bin[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", &chr[0], CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 23;
constant_type.typeinfo.scale = 3;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_DECIMAL;
constant_type.typeinfo.size = 19;
constant_type.typeinfo.scale = 4;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_DECIMAL;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 4;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_BINARY;
constant_type.typeinfo.size = 8;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TABLE", SQL_SS_TABLE, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_DATE;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIME2;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET;
constant_type.typeinfo.size = 34;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 27;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS );
// These constant are defined to provide type checking (type ==SQLSRV_SQLTYPE_DECIMAL).
// There are functions with the same name which accept parameters and is used in binding paramters.
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_DECIMAL", SQL_DECIMAL, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NUMERIC", SQL_NUMERIC, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_CHAR", SQL_CHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NCHAR", SQL_WCHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARCHAR", SQL_VARCHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NVARCHAR", SQL_WVARCHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_BINARY", SQL_BINARY, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARBINARY", SQL_VARBINARY, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS );
std::string fwd = "forward";
std::string stc = "static";
std::string dyn = "dynamic";
std::string key = "keyset";
std::string buf = "buffered";
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", &fwd[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", &stc[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", &dyn[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", &key[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", &buf[0], CONST_PERSISTENT | CONST_CS );
try {
// initialize list of warnings to ignore
g_ss_warnings_to_ignore_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 );
sqlsrv_error_const error_to_ignore;
// changed database warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 5701;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// changed language warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 5703;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// option value changed
error_to_ignore.sqlstate = (SQLCHAR*)"01S02";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// cursor operation conflict
error_to_ignore.sqlstate = (SQLCHAR*)"01001";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// null value eliminated in set function
error_to_ignore.sqlstate = (SQLCHAR*)"01003";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// SQL Azure warning: This session has been assigned a tracing id of ..
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 40608;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// Full-text search condition contained noise words warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 9927;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
}
catch( ss::SSException& ) {
LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" );
return FAILURE;
}
try {
// supported encodings
g_ss_encodings_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ );
sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR );
if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true );
if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 );
if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) {
throw ss::SSException();
}
}
catch( ss::SSException& ) {
LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
// initialize list of sqlsrv errors
g_ss_errors_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 );
for( int i = 0; SS_ERRORS[i].error_code != UINT_MAX; ++i ) {
if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[i].error_code,
&( SS_ERRORS[i].sqlsrv_error ), sizeof( SS_ERRORS[i].sqlsrv_error ))) {
LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ );
return FAILURE;
}
}
if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper ) == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ );
return FAILURE;
}
try {
// retrieve the handles for the environments
core_sqlsrv_minit( &g_ss_henv_cp, &g_ss_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" );
}
catch( core::CoreException& ) {
return FAILURE;
}
catch( ... ) {
LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." );
return FAILURE;
}
return SUCCESS;
}
// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed
void sqlsrv_error_const_dtor( _Inout_ zval* elem ) {
sqlsrv_error_const* error_to_ignore = static_cast<sqlsrv_error_const*>( Z_PTR_P(elem) );
pefree(error_to_ignore, 1);
}
// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed
void sqlsrv_encoding_dtor( _Inout_ zval* elem ) {
sqlsrv_encoding* sql_enc = static_cast<sqlsrv_encoding*>( Z_PTR_P(elem) );
pefree(sql_enc, 1);
}
// Module shutdown function
// Free the environment handles allocated in MINIT and unregister our stream wrapper.
// Resource types and constants are automatically released since we don't flag them as
// persistent when they are registered.
PHP_MSHUTDOWN_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( type );
UNREGISTER_INI_ENTRIES();
// clean up the list of sqlsrv errors
zend_hash_destroy( g_ss_errors_ht );
pefree( g_ss_errors_ht, 1 /*persistent*/ );
zend_hash_destroy( g_ss_warnings_to_ignore_ht );
pefree( g_ss_warnings_to_ignore_ht, 1 );
zend_hash_destroy( g_ss_encodings_ht );
pefree( g_ss_encodings_ht, 1 );
core_sqlsrv_mshutdown( *g_ss_henv_cp, *g_ss_henv_ncp );
if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER ) == FAILURE ) {
return FAILURE;
}
return SUCCESS;
}
// Request initialization function
// This function is called once per PHP script execution
// Initialize request globals used in the request, including those that correspond to INI entries.
// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not
// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored
// (such as return values from stored procedures), add them to this collection and they won't be
// returned as errors. Or you could just set WarningsReturnAsErrors to false.
PHP_RINIT_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( module_number );
// SQLSRV_UNUSED( type );
#if defined(ZTS)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
SQLSRV_G( warnings_return_as_errors ) = true;
ZVAL_NULL( &SQLSRV_G( errors ));
ZVAL_NULL( &SQLSRV_G( warnings ));
LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" );
// read INI settings
// need to convert const char[] to char[] to avoid converting string to char* warnings
// cannot simply cast const char[] to char* since INI_INT needs the sizeof(param) and the size of char* is always 4 / 8 bytes
char warnings_as_errors[] = INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS;
char severity[] = INI_PREFIX INI_LOG_SEVERITY;
char subsystems[] = INI_PREFIX INI_LOG_SUBSYSTEMS;
char buffered_limit[] = INI_PREFIX INI_BUFFERED_QUERY_LIMIT;
SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( warnings_as_errors );
SQLSRV_G( log_severity ) = INI_INT( severity );
SQLSRV_G( log_subsystems ) = INI_INT( subsystems );
SQLSRV_G( buffered_query_limit ) = INI_INT( buffered_limit );
#ifndef _WIN32
char set_locale_info[] = INI_PREFIX INI_SET_LOCALE_INFO;
SQLSRV_G(set_locale_info) = INI_INT(set_locale_info);
// if necessary, set locale from the environment for ODBC, which MUST be done before any connection
int set_locale = SQLSRV_G(set_locale_info);
if (set_locale == 2) {
setlocale(LC_ALL, "");
}
else if (set_locale == 1) {
setlocale(LC_CTYPE, "");
}
else {
// Do nothing
}
LOG(SEV_NOTICE, INI_PREFIX INI_SET_LOCALE_INFO " = %1!d!", set_locale);
#endif
LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off");
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity ));
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems ));
LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit ));
return SUCCESS;
}
// Request shutdown
// Called at the end of a script's execution
// Simply releases the variables allocated during request initialization.
PHP_RSHUTDOWN_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( module_number );
// SQLSRV_UNUSED( type );
LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" );
reset_errors();
// destruction
zval_ptr_dtor( &SQLSRV_G( errors ));
zval_ptr_dtor( &SQLSRV_G( warnings ));
return SUCCESS;
}
// Called for php_info(); Displays the INI settings registered and their current values
PHP_MINFO_FUNCTION(sqlsrv)
{
php_info_print_table_start();
php_info_print_table_header(2, "sqlsrv support", "enabled");
php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR);
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
// DllMain for the extension.
#ifdef _WIN32
// Only needed if extension is built shared
#ifdef COMPILE_DL_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,139 +0,0 @@
#ifndef PHP_SQLSRV_H
#define PHP_SQLSRV_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_sqlsrv.h
//
// Contents: Declarations for the extension
//
// Comments: Also contains "internal" declarations shared across source files.
//
// 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(sqlsrv)
// global objects for errors and warnings. These are returned by sqlsrv_errors.
zval errors;
zval warnings;
// flags for error handling and logging (set via sqlsrv_configure or php.ini)
zend_long log_severity;
zend_long log_subsystems;
zend_long current_subsystem;
zend_bool warnings_return_as_errors;
zend_long buffered_query_limit;
#ifndef _WIN32
zend_long set_locale_info;
#endif
ZEND_END_MODULE_GLOBALS(sqlsrv)
ZEND_EXTERN_MODULE_GLOBALS(sqlsrv);
// macro used to access the global variables. Use it to make global variable access agnostic to threads
#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v)
#if defined(ZTS)
ZEND_TSRMLS_CACHE_EXTERN();
#endif
//*********************************************************************************************************************************
// Initialization Functions
//*********************************************************************************************************************************
// variables set during initialization
extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP
#define phpext_sqlsrv_ptr &g_sqlsrv_module_entry
// module initialization
PHP_MINIT_FUNCTION(sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(sqlsrv);
//*********************************************************************************************************************************
// Functions
//*********************************************************************************************************************************
PHP_FUNCTION(sqlsrv_connect);
PHP_FUNCTION(sqlsrv_begin_transaction);
PHP_FUNCTION(sqlsrv_client_info);
PHP_FUNCTION(sqlsrv_close);
PHP_FUNCTION(sqlsrv_commit);
PHP_FUNCTION(sqlsrv_query);
PHP_FUNCTION(sqlsrv_prepare);
PHP_FUNCTION(sqlsrv_rollback);
PHP_FUNCTION(sqlsrv_server_info);
PHP_FUNCTION(sqlsrv_cancel);
PHP_FUNCTION(sqlsrv_execute);
PHP_FUNCTION(sqlsrv_fetch);
PHP_FUNCTION(sqlsrv_fetch_array);
PHP_FUNCTION(sqlsrv_fetch_object);
PHP_FUNCTION(sqlsrv_field_metadata);
PHP_FUNCTION(sqlsrv_free_stmt);
PHP_FUNCTION(sqlsrv_get_field);
PHP_FUNCTION(sqlsrv_has_rows);
PHP_FUNCTION(sqlsrv_next_result);
PHP_FUNCTION(sqlsrv_num_fields);
PHP_FUNCTION(sqlsrv_num_rows);
PHP_FUNCTION(sqlsrv_rows_affected);
PHP_FUNCTION(sqlsrv_send_stream_data);
// type functions for SQL types.
// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc.
// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added.
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY);
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL);
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC);
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY);
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR);
// PHP type functions
// strings and streams may have an encoding parameterized, so we use the functions
// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR.
PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM);
PHP_FUNCTION(SQLSRV_PHPTYPE_STRING);
// These functions set and retrieve configuration settings. Configuration settings defined are:
// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs.
// LogSeverity - combination of severity of messages to log (see Logging)
// LogSubsystems - subsystems within sqlsrv to log messages (see Logging)
PHP_FUNCTION(sqlsrv_configure);
PHP_FUNCTION(sqlsrv_get_config);
// *** extension error functions ***
PHP_FUNCTION(sqlsrv_errors);
#endif /* PHP_SQLSRV_H */

View file

@ -1,469 +0,0 @@
#ifndef PHP_SQLSRV_INT_H
#define PHP_SQLSRV_INT_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_sqlsrv_int.h
//
// Contents: Internal declarations for the extension
//
// Comments: Also contains "internal" declarations shared across source files.
//
// 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"
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
// INI settings and constants
// (these are defined as macros to allow concatenation as we do below)
#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors"
#define INI_LOG_SEVERITY "LogSeverity"
#define INI_LOG_SUBSYSTEMS "LogSubsystems"
#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize"
#define INI_PREFIX "sqlsrv."
#ifndef _WIN32
#define INI_SET_LOCALE_INFO "SetLocaleInfo"
#endif
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors,
zend_sqlsrv_globals, sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals,
sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals,
sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit,
zend_sqlsrv_globals, sqlsrv_globals )
#ifndef _WIN32
STD_PHP_INI_ENTRY(INI_PREFIX INI_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info,
zend_sqlsrv_globals, sqlsrv_globals)
#endif
PHP_INI_END()
//*********************************************************************************************************************************
// Initialization Functions
//*********************************************************************************************************************************
// module global variables (initialized in minit and freed in mshutdown)
extern HashTable* g_ss_errors_ht;
extern HashTable* g_ss_encodings_ht;
extern HashTable* g_ss_warnings_to_ignore_ht;
extern HMODULE g_sqlsrv_hmodule; // used for getting the version information
// henv context for creating connections
extern sqlsrv_context* g_ss_henv_cp;
extern sqlsrv_context* g_ss_henv_ncp;
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
struct ss_sqlsrv_conn : sqlsrv_conn
{
HashTable* stmts;
bool date_as_string;
bool format_decimals; // flag set to turn on formatting for values of decimal / numeric types
short decimal_places; // number of decimal digits to show in a result set unless format_numbers is false
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
// static variables used in process_params
static const char* resource_name;
static int descriptor;
// initialize with default values
ss_sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv ) :
sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM ),
stmts( NULL ),
date_as_string( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
in_transaction( false )
{
}
};
// resource destructor
void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field_name {
char* name;
SQLLEN len;
};
struct stmt_option_ss_scrollable : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
// This object inherits and overrides the callbacks necessary
struct ss_sqlsrv_stmt : public sqlsrv_stmt {
ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv );
virtual ~ss_sqlsrv_stmt( void );
void new_result_set( void );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream );
bool prepared; // whether the statement has been prepared yet (used for error messages)
zend_ulong conn_index; // index into the connection hash that contains this statement structure
zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute
sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys
int fetch_fields_count;
// static variables used in process_params
static const char* resource_name;
static int descriptor;
};
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field {
char* name;
unsigned int len;
};
// holds the stream param and the encoding that it was assigned
struct sqlsrv_stream_encoding {
zval* stream_z;
unsigned int encoding;
sqlsrv_stream_encoding( _In_ zval* str_z, _In_ unsigned int enc ) :
stream_z( str_z ), encoding( enc )
{
}
};
// resource destructor
void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc );
// "internal" statement functions shared by functions in conn.cpp and stmt.cpp
void bind_params( _Inout_ ss_sqlsrv_stmt* stmt );
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function
);
void free_odbc_resources( ss_sqlsrv_stmt* stmt );
void free_stmt_resource( _Inout_ zval* stmt_z );
//*********************************************************************************************************************************
// Errors
//*********************************************************************************************************************************
// represents the mapping between an error_code and the corresponding error message.
struct ss_error {
unsigned int error_code;
sqlsrv_error_const sqlsrv_error;
};
// List of all driver specific error codes.
enum SS_ERROR_CODES {
SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC,
SS_SQLSRV_ERROR_NOT_IN_TXN,
SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER,
SS_SQLSRV_ERROR_REGISTER_RESOURCE,
SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY,
SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE,
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE,
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED,
SS_SQLSRV_ERROR_ZEND_BAD_CLASS,
SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE,
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE,
SS_SQLSRV_ERROR_INVALID_OPTION,
SS_SQLSRV_ERROR_PARAM_INVALID_INDEX,
SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION,
SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION,
SS_SQLSRV_ERROR_VAR_REQUIRED,
SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING,
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF,
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED
};
extern ss_error SS_ERRORS[];
bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args );
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string.
unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string,
_In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer(mbcs_in_string) wchar_t* utf16_out_string,
_In_ unsigned int utf16_len, bool use_strict_conversion = false );
// create a wide char string from the passed in mbcs string. NULL is returned if the string
// could not be created. No error is posted by this function. utf16_len is the number of
// wchar_t characters, not the number of bytes.
SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string,
_In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len, bool use_strict_conversion = false );
// *** internal error macros and functions ***
bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp, ... );
void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp, ... );
void __cdecl sqlsrv_error_dtor( zend_resource *rsrc );
// release current error lists and set to NULL
inline void reset_errors( void )
{
if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) {
DIE( "sqlsrv_errors contains an invalid type" );
}
if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) {
DIE( "sqlsrv_warnings contains an invalid type" );
}
if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors )));
FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors )));
}
if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings )));
FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings )));
}
ZVAL_NULL( &SQLSRV_G( errors ));
ZVAL_NULL( &SQLSRV_G( warnings ));
}
#define THROW_SS_ERROR( ctx, error_code, ... ) \
(void)call_error_handler( ctx, error_code, 0 /*warning*/, ## __VA_ARGS__ ); \
throw ss::SSException();
class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > {
public:
sqlsrv_context_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_context, sqlsrv_context_auto_ptr >( NULL )
{
}
sqlsrv_context_auto_ptr( _Inout_opt_ const sqlsrv_context_auto_ptr& src ) :
sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( _In_opt_ sqlsrv_context* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_context();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_context* operator=( _In_opt_ sqlsrv_context* ptr )
{
return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr );
}
void operator=( _Inout_opt_ sqlsrv_context_auto_ptr& src )
{
sqlsrv_context* p = src.get();
src.transferred();
this->_ptr = p;
}
};
//*********************************************************************************************************************************
// Logging
//*********************************************************************************************************************************
#define LOG_FUNCTION( function_name ) \
const char* _FN_ = function_name; \
SQLSRV_G( current_subsystem ) = current_log_subsystem; \
core_sqlsrv_register_severity_checker(ss_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", _FN_);
// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool ss_severity_check(_In_ unsigned int severity);
// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise.
enum logging_subsystems {
LOG_INIT = 0x01,
LOG_CONN = 0x02,
LOG_STMT = 0x04,
LOG_UTIL = 0x08,
LOG_ALL = -1,
};
//*********************************************************************************************************************************
// Common function wrappers
// have to place this namespace before the utility functions
// otherwise can't compile in Linux because 'ss' not defined
//*********************************************************************************************************************************
namespace ss {
// an error which occurred in our SQLSRV driver
struct SSException : public core::CoreException {
SSException()
{
}
};
inline void zend_register_resource( _Inout_ zval& rsrc_result, _Inout_ void* rsrc_pointer, _In_ int rsrc_type, _In_opt_ const char* rsrc_name)
{
int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE);
CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast<sqlsrv_context*>( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE,
rsrc_name ) {
throw ss::SSException();
}
Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX;
}
} // namespace ss
//*********************************************************************************************************************************
// Utility Functions
//*********************************************************************************************************************************
// generic function used to validate parameters to a PHP function.
// Register an invalid parameter error and returns NULL when parameters don't match the spec given.
template <typename H>
inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_spec, _In_ const char* calling_func, _In_ size_t param_count, ... )
{
// SQLSRV_UNUSED( return_value );
zval* rsrc;
H* h = NULL;
// reset the errors from the previous API call
reset_errors();
if( ZEND_NUM_ARGS() > param_count + 1 ) {
DIE( "Param count and argument count don't match." );
return NULL; // for static analysis tools
}
try {
if( param_count > 6 ) {
DIE( "Param count cannot exceed 6" );
return NULL; // for static analysis tools
}
void* arr[6];
va_list vaList;
va_start(vaList, param_count); //set the pointer to first argument
for(size_t i = 0; i < param_count; ++i) {
arr[i] = va_arg(vaList, void*);
}
va_end(vaList);
int result = SUCCESS;
// dummy context to pass to the error handler
sqlsrv_context error_ctx( 0, ss_error_handler, NULL );
error_ctx.set_func( calling_func );
switch( param_count ) {
case 0:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc );
break;
case 1:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0] );
break;
case 2:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1] );
break;
case 3:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2] );
break;
case 4:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3] );
break;
case 5:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3], arr[4] );
break;
case 6:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3], arr[4], arr[5] );
break;
default:
{
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func );
break;
}
}
CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) {
throw ss::SSException();
}
// get the resource registered
h = static_cast<H*>( zend_fetch_resource(Z_RES_P(rsrc), H::resource_name, H::descriptor ));
CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) {
throw ss::SSException();
}
h->set_func( calling_func );
}
catch( core::CoreException& ) {
return NULL;
}
catch ( ... ) {
DIE( "%1!s!: Unknown exception caught in process_params.", calling_func );
}
return h;
}
#endif /* PHP_SQLSRV_INT_H */

View file

@ -1,2215 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: stmt.cpp
//
// Contents: Routines that use statement handles
//
// 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.
//---------------------------------------------------------------------------------------------------------------------------------
// *** header files ***
extern "C" {
#include "php_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
#ifdef _WIN32
#include <sal.h>
#endif // _WIN32
//
// *** internal variables and constants ***
//
// our resource descriptor assigned in minit
int ss_sqlsrv_stmt::descriptor = 0;
const char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt";
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_STMT;
// constants used as invalid types for type errors
const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID;
const int SQLSRV_INVALID_PRECISION = -1;
const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U);
const int SQLSRV_INVALID_SCALE = -1;
const int SQLSRV_SIZE_MAX_TYPE = -1;
// constants for maximums in SQL Server
const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
const int SQL_SERVER_MAX_PRECISION = 38;
// default class used when no class is specified by sqlsrv_fetch_object
const char STDCLASS_NAME[] = "stdclass";
const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1;
// map a Zend PHP type constant to our constant type
enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_NULL,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_TABLE,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STREAM,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID
};
// constant strings used for the field metadata results
// (char to avoid having to cast them where they are used)
namespace FieldMetaData {
const char* NAME = "Name";
const char* TYPE = "Type";
const char* SIZE = "Size";
const char* PREC = "Precision";
const char* SCALE = "Scale";
const char* NULLABLE = "Nullable";
}
/* internal functions */
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval );
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt);
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names );
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
_Out_ SQLSMALLINT* decimal_digits );
sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string );
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt );
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type );
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding );
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index,
_Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type,
_Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits);
}
// query options for cursor types
namespace SSCursorTypes {
const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static";
const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic";
const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset";
const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward";
const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered";
}
ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) :
sqlsrv_stmt( c, handle, e, drv ),
prepared( false ),
conn_index( -1 ),
params_z( NULL ),
fetch_field_names( NULL ),
fetch_fields_count ( 0 )
{
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) );
// inherit other values based on the corresponding connection options
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
date_as_string = ss_conn->date_as_string;
format_decimals = ss_conn->format_decimals;
decimal_places = ss_conn->decimal_places;
}
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
{
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
sqlsrv_free( fetch_field_names[i].name );
}
sqlsrv_free( fetch_field_names );
}
if( params_z ) {
zval_ptr_dtor( params_z );
sqlsrv_free(params_z);
}
}
// to be called whenever a new result set is created, such as after an
// execute or next_result. Resets the state variables and calls the subclass.
void ss_sqlsrv_stmt::new_result_set( void )
{
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
sqlsrv_free( fetch_field_names[i].name );
}
sqlsrv_free( fetch_field_names );
}
fetch_field_names = NULL;
fetch_fields_count = 0;
sqlsrv_stmt::new_result_set();
}
// Returns a php type for a given sql type. Also sets the encoding wherever applicable.
sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream )
{
sqlsrv_phptype ss_phptype;
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
switch( sql_type ) {
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_WCHAR:
case SQL_SS_VARIANT:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
if( prefer_string_to_stream ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
break;
case SQL_FLOAT:
case SQL_REAL:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
break;
case SQL_SS_TABLE:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
if (this->date_as_string) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
default:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
break;
}
return ss_phptype;
}
// statement specific parameter proccessing. Uses the generic function specialised to return a statement
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
rsrc = process_params<ss_sqlsrv_stmt>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ## __VA_ARGS__ );\
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
// sqlsrv_execute( resource $stmt )
//
// Executes a previously prepared statement. See sqlsrv_prepare for information
// on preparing a statement for execution.
//
// This function is ideal for executing a prepared statement multiple times with
// different parameter values. See the MSDN documentation
//
// Parameters
// $stmt: A resource specifying the statement to be executed. For more
// information about how to create a statement resource, see sqlsrv_prepare.
//
// Return Value
// A Boolean value: true if the statement was successfully executed. Otherwise, false.
PHP_FUNCTION( sqlsrv_execute )
{
LOG_FUNCTION( "sqlsrv_execute" );
ss_sqlsrv_stmt* stmt = NULL;
try {
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) {
throw ss::SSException();
}
// prepare for the next execution by flushing anything remaining in the result set
if( stmt->executed ) {
// to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too)
while( stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( stmt, false, false );
}
}
// bind parameters before executing
bind_params( stmt );
core_sqlsrv_execute( stmt );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_execute: Unknown exception caught." );
}
}
// sqlsrv_fetch( resource $stmt )
//
// Makes the next row of a result set available for reading. Use
// sqlsrv_get_field to read fields of the row.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement. A
// statement must be executed before results can be retrieved. For information
// on executing a statement, see sqlsrv_query and sqlsrv_execute.
//
// Return Value
// If the next row of the result set was successfully retrieved, true is
// returned. If there are no more results in the result set, null is
// returned. If an error occured, false is returned
PHP_FUNCTION( sqlsrv_fetch )
{
LOG_FUNCTION( "sqlsrv_fetch" );
ss_sqlsrv_stmt* stmt = NULL;
// NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
// take only the statement resource
PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
if( !result ) {
RETURN_NULL();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch: Unknown exception caught." );
}
}
// sqlsrv_fetch_array( resource $stmt [, int $fetchType] )
//
// Retrieves the next row of data as an array.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h
//
// Return Value
// If a row of data is retrieved, an array is returned. If there are no more
// rows to retrieve, null is returned. If an error occurs, false is returned.
// Based on the value of the $fetchType parameter, the returned array can be a
// numerically indexed array, an associative array, or both. By default, an
// array with both numeric and associative keys is returned. The data type of a
// value in the returned array will be the default PHP data type. For
// information about default PHP data types, see Default PHP Data Types.
PHP_FUNCTION( sqlsrv_fetch_array )
{
LOG_FUNCTION( "sqlsrv_fetch_array" );
ss_sqlsrv_stmt* stmt = NULL;
zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
if( !result ) {
RETURN_NULL();
}
zval fields;
ZVAL_UNDEF( &fields );
fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ );
RETURN_ARR( Z_ARRVAL( fields ));
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch_array: Unknown exception caught." );
}
}
// sqlsrv_field_metadata( resource $stmt )
//
// Retrieves metadata for the fields of a prepared statement. For information
// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that
// sqlsrv_field_metadata can be called on any prepared statement, pre- or
// post-execution.
//
// Parameters
// $stmt: A statement resource for which field metadata is sought.
//
// Return Value
// retrieve an array of metadata for the current result set on a statement. Each element of the
// array is a sub-array containing 5 elements accessed by key:
// name - name of the field.
// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants.
// size - length of the field. null if the field uses precision and scale instead.
// precision - number of digits in a numeric field. null if the field uses size.
// scale - number of decimal digits in a numeric field. null if the field uses sizes.
// is_nullable - if the field may contain a NULL instead of a value
// false is returned if an error occurs retrieving the metadata
PHP_FUNCTION( sqlsrv_field_metadata )
{
sqlsrv_stmt* stmt = NULL;
LOG_FUNCTION( "sqlsrv_field_metadata" );
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// get the number of fields in the resultset and its metadata if not exists
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
if (stmt->data_classification) {
core_sqlsrv_sensitivity_metadata(stmt);
}
zval result_meta_data;
ZVAL_UNDEF(&result_meta_data);
array_init(&result_meta_data);
for( SQLSMALLINT f = 0; f < num_cols; ++f ) {
field_meta_data* core_meta_data = stmt->current_meta_data[f];
// initialize the array
zval field_array;
ZVAL_UNDEF( &field_array );
array_init(&field_array );
// add the field name to the associative array but keep a copy
add_assoc_string(&field_array, FieldMetaData::NAME, reinterpret_cast<char*>(core_meta_data->field_name.get()));
//core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type );
add_assoc_long(&field_array, FieldMetaData::TYPE, core_meta_data->field_type);
switch( core_meta_data->field_type ) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
add_assoc_null(&field_array, FieldMetaData::SIZE);
add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision);
add_assoc_long(&field_array, FieldMetaData::SCALE, core_meta_data->field_scale);
break;
case SQL_BIT:
case SQL_TINYINT:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_BIGINT:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
add_assoc_null(&field_array, FieldMetaData::SIZE);
add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision);
add_assoc_null(&field_array, FieldMetaData::SCALE);
break;
default:
add_assoc_long(&field_array, FieldMetaData::SIZE, core_meta_data->field_size);
add_assoc_null(&field_array, FieldMetaData::PREC);
add_assoc_null(&field_array, FieldMetaData::SCALE);
break;
}
// add the nullability to the array
add_assoc_long(&field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable);
if (stmt->data_classification) {
data_classification::fill_column_sensitivity_array(stmt, f, &field_array);
}
// add this field's meta data to the result set meta data
add_next_index_zval(&result_meta_data, &field_array);
}
// return our built collection and transfer ownership
RETURN_ZVAL(&result_meta_data, 1, 1);
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_field_metadata: Unknown exception caught." );
}
}
// sqlsrv_next_result( resource $stmt )
//
// Makes the next result (result set, row count, or output parameter) of the
// specified statement active. The first (or only) result returned by a batch
// query or stored procedure is active without a call to sqlsrv_next_result.
// Any output parameters bound are only available after sqlsrv_next_result returns
// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx
//
// Parameters
// $stmt: The executed statement on which the next result is made active.
//
// Return Value
// If the next result was successfully made active, the Boolean value true is
// returned. If an error occurred in making the next result active, false is
// returned. If no more results are available, null is returned.
PHP_FUNCTION( sqlsrv_next_result )
{
LOG_FUNCTION( "sqlsrv_next_result" );
ss_sqlsrv_stmt* stmt = NULL;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
core_sqlsrv_next_result( stmt, true );
// clear the current meta data since the new result will generate new meta data
stmt->clean_up_results_metadata();
if( stmt->past_next_result_end ) {
RETURN_NULL();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_next_result: Unknown exception caught." );
}
}
// sqlsrv_rows_affected( resource $stmt )
//
// Returns the number of rows modified by the last statement executed. This
// function does not return the number of rows returned by a SELECT statement.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// Return Value
// An integer indicating the number of rows modified by the last executed
// statement. If no rows were modified, zero (0) is returned. If no information
// about the number of modified rows is available, negative one (-1) is
// returned. If an error occurred in retrieving the number of modified rows,
// false is returned. See SQLRowCount in the MSDN ODBC documentation.
PHP_FUNCTION( sqlsrv_rows_affected )
{
LOG_FUNCTION( "sqlsrv_rows_affected" );
ss_sqlsrv_stmt* stmt = NULL;
SQLLEN rows = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// make sure that the statement has already been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
// make sure it is not scrollable. This function should only work for inserts, updates, and deletes,
// but this is the best we can do to enforce that.
CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) {
throw ss::SSException();
}
rows = stmt->current_results->row_count();
RETURN_LONG( rows );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_rows_affected: Unknown exception caught." );
}
}
// sqlsrv_num_rows( resource $stmt )
//
// Retrieves the number of rows in an active result set. The statement must
// have been created with the Scrollable attribute set to 'static'.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of rows in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_rows )
{
LOG_FUNCTION( "sqlsrv_num_rows" );
ss_sqlsrv_stmt* stmt = NULL;
SQLLEN rows = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// make sure that the statement has already been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
// make sure that the statement is scrollable and the cursor is not dynamic.
// if the cursor is dynamic, then the number of rows returned is always -1.
CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt,
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) {
throw ss::SSException();
}
rows = stmt->current_results->row_count();
RETURN_LONG( rows );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_num_rows: Unknown exception caught." );
}
}
// sqlsrv_num_fields( resource $stmt )
//
// Retrieves the number of fields in an active result set. Note that
// sqlsrv_num_fields can be called on any prepared statement, before or after
// execution.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of fields in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_fields )
{
LOG_FUNCTION( "sqlsrv_num_fields" );
ss_sqlsrv_stmt* stmt = NULL;
SQLSMALLINT fields = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// retrieve the number of columns from ODBC
fields = core::SQLNumResultCols( stmt );
RETURN_LONG( fields );
}
catch( ss::SSException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_num_fields: Unknown exception caught." );
}
}
// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams]])
//
// Retrieves the next row of data as a PHP object.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// $className [OPTIONAL]: A string specifying the name of the class to
// instantiate. If a value for the $className parameter is not specified, an
// instance of the PHP stdClass is instantiated.
//
// $ctorParams [OPTIONAL]: An array that contains values passed to the
// constructor of the class specified with the $className parameter. If the
// constructor of the specified class accepts parameter values, the $ctorParams
// parameter must be used when calling sqlsrv_fetch_object.
//
// Return Value
// A PHP object with properties that correspond to result set field
// names. Property values are populated with the corresponding result set field
// values. If the class specified with the optional $className parameter does
// not exist or if there is no active result set associated with the specified
// statement, false is returned.
// The data type of a value in the returned object will be the default PHP data
// type. For information on default PHP data types, see Default PHP Data Types.
//
// Remarks
// If a class name is specified with the optional $className parameter, an
// object of this class type is instantiated. If the class has properties whose
// names match the result set field names, the corresponding result set values
// are applied to the properties. If a result set field name does not match a
// class property, a property with the result set field name is added to the
// object and the result set value is applied to the property. For more
// information about calling sqlsrv_fetch_object with the $className parameter,
// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server).
//
// If a field with no name is returned, sqlsrv_fetch_object will discard the
// field value and issue a warning.
PHP_FUNCTION( sqlsrv_fetch_object )
{
LOG_FUNCTION( "sqlsrv_fetch_object" );
ss_sqlsrv_stmt* stmt = NULL;
zval* class_name_z = NULL;
zval* ctor_params_z = NULL;
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
// stdClass is the name of the system's default base class in PHP
char* class_name = const_cast<char*>( STDCLASS_NAME );
std::size_t class_name_len = STDCLASS_NAME_LEN;
HashTable* properties_ht = NULL;
zval retval_z;
ZVAL_UNDEF( &retval_z );
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
// we also use z! instead of s and a so that null may be passed in as valid values for
// the class name and ctor params
PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
if( class_name_z ) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
class_name = Z_STRVAL( *class_name_z );
class_name_len = Z_STRLEN( *class_name_z );
}
if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
// fetch the data
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
if( !result ) {
RETURN_NULL();
}
fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ );
properties_ht = Z_ARRVAL( retval_z );
// find the zend_class_entry of the class the user requested (stdClass by default) for use below
zend_class_entry* class_entry = NULL;
zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 );
int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z ))) ? SUCCESS : FAILURE;
zend_string_release( class_name_str_z );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) {
throw ss::SSException();
}
// create an instance of the object with its default properties
// we pass NULL for the properties so that the object will be populated by its default properties
zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
// merge in the "properties" (associative array) returned from the fetch doing this vice versa
// since putting properties_ht into object_and_properties_init and merging the default properties
// causes duplicate properties when the visibilities are different and also references the
// default parameters directly in the object, meaning the default property value is changed when
// the object's property is changed.
zend_merge_properties( &retval_z, properties_ht );
zend_hash_destroy( properties_ht );
FREE_HASHTABLE( properties_ht );
// find and call the object's constructor
// The header files (zend.h and zend_API.h) declare
// these functions and structures, so by working with those, we were able to
// develop this as a suitable snippet for calling constructors. Some observations:
// params must be an array of zval**, not a zval** to an array as we originally
// thought. Also, a constructor doesn't show up in the function table, but
// is put into the "magic methods" section of the class entry.
//
// The default values of the fci and fcic structures were determined by
// calling zend_fcall_info_init with a test callable.
// if there is a constructor (e.g., stdClass doesn't have one)
if( class_entry->constructor ) {
// take the parameters given as our last argument and put them into a sequential array
sqlsrv_malloc_auto_ptr<zval> params_m;
zval ctor_retval_z;
ZVAL_UNDEF( &ctor_retval_z );
int num_params = 0;
if ( ctor_params_z ) {
HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z );
num_params = zend_hash_num_elements( ctor_params_ht );
params_m = reinterpret_cast<zval*>( sqlsrv_malloc( num_params * sizeof( zval ) ));
int i = 0;
zval* value_z = NULL;
ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) {
zr = ( value_z ) ? SUCCESS : FAILURE;
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
ZVAL_COPY_VALUE(&params_m[i], value_z);
i++;
} ZEND_HASH_FOREACH_END();
} //if( !Z_ISUNDEF( ctor_params_z ))
// call the constructor function itself.
zend_fcall_info fci;
zend_fcall_info_cache fcic;
memset( &fci, 0, sizeof( fci ));
fci.size = sizeof( fci );
#if PHP_VERSION_ID < 70100
fci.function_table = &( class_entry )->function_table;
#endif
ZVAL_UNDEF( &( fci.function_name ) );
fci.retval = &ctor_retval_z;
fci.param_count = num_params;
fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred.
fci.object = Z_OBJ_P( &retval_z );
memset( &fcic, 0, sizeof( fcic ));
#if PHP_VERSION_ID < 70300
fcic.initialized = 1;
#endif
fcic.function_handler = class_entry->constructor;
fcic.calling_scope = class_entry;
fcic.object = Z_OBJ_P( &retval_z );
zr = zend_call_function( &fci, &fcic );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
} //if( class_entry->constructor )
RETURN_ZVAL( &retval_z, 1, 1 );
}
catch( core::CoreException& ) {
if( properties_ht != NULL ) {
zend_hash_destroy( properties_ht );
FREE_HASHTABLE( properties_ht );
}
else if ( Z_TYPE( retval_z ) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL( retval_z ));
FREE_HASHTABLE( Z_ARRVAL( retval_z ));
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch_object: Unknown exception caught." );
}
}
// sqlsrv_has_rows( resource $stmt )
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// Returns whether or not there are rows waiting to be processed. There are two scenarios
// for using a function like this:
// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this.
// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least
// one row of data.
// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as
// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed
// until sqlsrv_fetch returns NULL.
// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error
// most likely means that there is no result data anyways.
// If this functions returs true one time, then it will return true even after the result set is exhausted
// (sqlsrv_fetch returns null)
PHP_FUNCTION( sqlsrv_has_rows )
{
LOG_FUNCTION( "sqlsrv_has_rows" );
ss_sqlsrv_stmt* stmt = NULL;
try {
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
if( !stmt->has_rows && !stmt->fetch_called ) {
determine_stmt_has_rows( stmt );
}
if( stmt->has_rows ) {
RETURN_TRUE;
}
}
catch( core::CoreException& ) {
}
catch( ... ) {
DIE( "sqlsrv_has_rows: Unknown exception caught." );
}
RETURN_FALSE;
}
// sqlsrv_send_stream_data( resource $stmt )
//
// Sends data from parameter streams to the server. Up to eight kilobytes (8K)
// of data is sent with each call to sqlsrv_send_stream_data.
// By default, all stream data is sent to the server when a query is
// executed. If this default behavior is not changed, you do not have to use
// sqlsrv_send_stream_data to send stream data to the server. For information
// about changing the default behavior, see the Parameters section of
// sqlsrv_query or sqlsrv_prepare.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// Return Value
// true if there is more data to be sent. null, if all the data has been sent,
// and false if an error occurred
PHP_FUNCTION( sqlsrv_send_stream_data )
{
sqlsrv_stmt* stmt = NULL;
LOG_FUNCTION( "sqlsrv_send_stream_data" );
// get the statement resource that we've bound streams to
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// if everything was sent at execute time, just return that there is nothing more to send.
if( stmt->send_streams_at_exec ) {
RETURN_NULL();
}
// send the next packet
bool more = core_sqlsrv_send_stream_packet( stmt );
// if more to send, return true
if( more ) {
RETURN_TRUE;
}
// otherwise we're done, so return null
else {
RETURN_NULL();
}
}
catch( core::CoreException& ) {
// return false if an error occurred
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_send_stream_data: Unknown exception caught." );
}
}
// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] )
//
// Retrieves data from the specified field of the current row. Field data must
// be accessed in order. For example, data from the first field cannot be
// accessed after data from the second field has been accessed.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero.
// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines
// the PHP data type for the returned data. For information about supported data
// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return
// type is specified, a default PHP type will be returned. For information about
// default PHP types, see Default PHP Data Types. For information about
// specifying PHP data types, see How to: Specify PHP Data Types.
//
// Return Value
// The field data. You can specify the PHP data type of the returned data by
// using the $getAsType parameter. If no return data type is specified, the
// default PHP data type will be returned. For information about default PHP
// types, see Default PHP Data Types. For information about specifying PHP data
// types, see How to: Specify PHP Data Types.
PHP_FUNCTION( sqlsrv_get_field )
{
LOG_FUNCTION( "sqlsrv_get_field" );
ss_sqlsrv_stmt* stmt = NULL;
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
void* field_value = NULL;
zend_long field_index = -1;
SQLLEN field_len = -1;
zval retval_z;
ZVAL_UNDEF(&retval_z);
// get the statement, the field index and the optional type
PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type );
try {
// validate that the field index is within range
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
if( field_index < 0 || field_index >= num_cols ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
core_sqlsrv_get_field( stmt, static_cast<SQLUSMALLINT>( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/,
&sqlsrv_php_type_out );
convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z );
sqlsrv_free( field_value );
RETURN_ZVAL( &retval_z, 1, 1 );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_get_field: Unknown exception caught." );
}
}
// ** type functions. **
// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions
// to match that notation and return a specially encoded integer that tells us what type and size/precision
// are. For PHP types specifically we munge the type and encoding into the integer.
// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined
// below.
// takes an encoding of the stream
PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM )
{
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM );
}
// takes an encoding of the string
PHP_FUNCTION( SQLSRV_PHPTYPE_STRING )
{
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING );
}
// takes the size of the binary field
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY );
}
// takes the size of the char field
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR );
}
// takes the precision and scale of the decimal field
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL)
{
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL );
}
// takes the size of the nchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR );
}
// takes the precision and scale of the numeric field
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC)
{
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC );
}
// takes the size (in characters, not bytes) of the nvarchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR );
}
// takes the size of the varbinary field
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY );
}
// takes the size of the varchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR );
}
void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
{
// if there's nothing to do, just return
if( stmt->params_z == NULL ) {
return;
}
try {
stmt->executed = false;
zval* params_z = stmt->params_z;
HashTable* params_ht = Z_ARRVAL_P( params_z );
zend_ulong index = -1;
zend_string *key = NULL;
zval* param_z = NULL;
ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) {
// make sure it's an integer index
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX) {
throw ss::SSException();
}
zval* value_z = NULL;
SQLSMALLINT direction = SQL_PARAM_INPUT;
SQLSRV_ENCODING encoding = stmt->encoding();
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
SQLSMALLINT decimal_digits = 0;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
// if it's a parameter array
if (Z_TYPE_P(param_z) == IS_ARRAY) {
try {
HashTable* param_ht = Z_ARRVAL_P(param_z);
// Check the number of elements in the array
int num_elems = zend_hash_num_elements(param_ht);
if (num_elems > 1) {
value_z = parse_param_array(stmt, param_ht, index, direction, php_out_type, encoding, sql_type, column_size, decimal_digits);
} else {
// Simply get the first variable and use the defaults
value_z = zend_hash_index_find(param_ht, 0);
if (value_z == NULL) {
THROW_SS_ERROR(stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1);
}
}
} catch (core::CoreException&) {
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
throw;
}
}
else {
CHECK_CUSTOM_ERROR(!stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
throw ss::SSException();
}
value_z = param_z;
}
// If the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_LONGVARCHAR
|| sql_type == SQL_WLONGVARCHAR || sql_type == SQL_LONGVARBINARY),
stmt, SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED) {
throw core::CoreException();
}
// Table-valued parameters are input-only
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_SS_TABLE || php_out_type == SQLSRV_PHPTYPE_TABLE), stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
throw ss::SSException();
}
// bind the parameter
core_sqlsrv_bind_param( stmt, static_cast<SQLUSMALLINT>( index ), direction, value_z, php_out_type, encoding, sql_type, column_size,
decimal_digits );
} ZEND_HASH_FOREACH_END();
}
catch( core::CoreException& ) {
stmt->free_param_data();
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
zval_ptr_dtor( stmt->params_z );
sqlsrv_free( stmt->params_z );
stmt->params_z = NULL;
throw;
}
}
// sqlsrv_cancel( resource $stmt )
//
// Cancels a statement. This means that any pending results for the statement
// are discarded. After this function is called, the statement can be
// re-executed if it was prepared with sqlsrv_prepare. Calling this function is
// not necessary if all the results associated with the statement have been
// consumed.
//
// Parameters
// $stmt: The statement to be canceled.
//
// Return Value
// A Boolean value: true if the operation was successful. Otherwise, false.
PHP_FUNCTION( sqlsrv_cancel )
{
LOG_FUNCTION( "sqlsrv_cancel" );
ss_sqlsrv_stmt* stmt = NULL;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// close the stream to release the resource
close_active_stream( stmt );
SQLRETURN r = SQLCancel( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw ss::SSException();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_cancel: Unknown exception caught." );
}
}
void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc )
{
LOG_FUNCTION( "sqlsrv_stmt_dtor" );
// get the structure
ss_sqlsrv_stmt *stmt = static_cast<ss_sqlsrv_stmt*>( rsrc->ptr );
if( stmt->conn ) {
int zr = zend_hash_index_del( static_cast<ss_sqlsrv_conn*>( stmt->conn )->stmts, stmt->conn_index );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to remove statement reference from the connection" );
}
}
stmt->~ss_sqlsrv_stmt();
sqlsrv_free( stmt );
rsrc->ptr = NULL;
}
// sqlsrv_free_stmt( resource $stmt )
//
// Frees all resources associated with the specified statement. The statement
// cannot be used again after this function has been called.
//
// Parameters
// $stmt: The statement to be closed.
//
// Return Value
// The Boolean value true unless the function is called with an invalid
// parameter. If the function is called with an invalid parameter, false is
// returned.
//
// Null is a valid parameter for this function. This allows the function to be
// called multiple times in a script. For example, if you free a statement in an
// error condition and free it again at the end of the script, the second call
// to sqlsrv_free_stmt will return true because the first call to
// sqlsrv_free_stmt (in the error condition) sets the statement resource to
// null.
PHP_FUNCTION( sqlsrv_free_stmt )
{
LOG_FUNCTION( "sqlsrv_free_stmt" );
zval* stmt_r = NULL;
ss_sqlsrv_stmt* stmt = NULL;
sqlsrv_context_auto_ptr error_ctx;
reset_errors();
try {
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
// take only the statement resource
if( zend_parse_parameters( ZEND_NUM_ARGS(), "r", &stmt_r ) == FAILURE ) {
// Check if it was a zval
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &stmt_r );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
if( Z_TYPE_P( stmt_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
// verify the resource so we know we're deleting a statement
stmt = static_cast<ss_sqlsrv_stmt*>(zend_fetch_resource_ex(stmt_r, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor));
// if sqlsrv_free_stmt was called on an already closed statment then we just return success.
// zend_list_close sets the type of the closed statment to -1.
SQLSRV_ASSERT( stmt_r != NULL, "sqlsrv_free_stmt: stmt_r is null." );
if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) {
RETURN_TRUE;
}
if( stmt == NULL ) {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
// delete the resource from Zend's master list, which will trigger the statement's destructor
#if PHP_VERSION_ID < 80000
if (zend_list_close(Z_RES_P(stmt_r)) == FAILURE) {
LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P(stmt_r)->handle);
}
#else
zend_list_close(Z_RES_P(stmt_r));
#endif
// when stmt_r is first parsed in zend_parse_parameters, stmt_r becomes a zval that points to a zend_resource with a refcount of 2
// need to DELREF here so the refcount becomes 1 and stmt_r can be appropriate destroyed by the garbage collector when it goes out of scope
// zend_list_close only destroy the resource pointed to by Z_RES_P( stmt_r ), not the zend_resource itself
Z_TRY_DELREF_P(stmt_r);
ZVAL_NULL( stmt_r );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_free_stmt: Unknown exception caught." );
}
}
void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) {
throw ss::SSException();
}
const char* scroll_type = Z_STRVAL_P( value_z );
unsigned long cursor_type = -1;
// find which cursor type they would like and set the ODBC statement attribute as such
if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) {
cursor_type = SQL_CURSOR_STATIC;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) {
cursor_type = SQL_CURSOR_DYNAMIC;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) {
cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) {
cursor_type = SQL_CURSOR_FORWARD_ONLY;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) {
cursor_type = SQLSRV_CURSOR_BUFFERED;
}
else {
THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
}
core_sqlsrv_set_scrollable( stmt, cursor_type );
}
namespace {
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval)
{
if ( in_val == NULL ) {
ZVAL_NULL( &out_zval);
return;
}
switch (sqlsrv_php_type) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) {
ZVAL_LONG( &out_zval, *(static_cast<int*>( in_val )));
}
else {
ZVAL_DOUBLE( &out_zval, *(static_cast<double*>( in_val )));
}
break;
}
case SQLSRV_PHPTYPE_STRING:
{
ZVAL_STRINGL( &out_zval, static_cast<const char*>( in_val ), field_len);
break;
}
case SQLSRV_PHPTYPE_STREAM:
{
out_zval = *( static_cast<zval*>( in_val ));
stmt->active_stream = out_zval;
//addref here because deleting out_zval later will decrement the refcount
Z_TRY_ADDREF( out_zval );
break;
}
case SQLSRV_PHPTYPE_DATETIME:
{
convert_datetime_string_to_zval(stmt, static_cast<char*>(in_val), field_len, out_zval);
break;
}
case SQLSRV_PHPTYPE_NULL:
ZVAL_NULL(&out_zval);
break;
default:
DIE("Unknown php type");
break;
}
return;
}
// put in the column size and scale/decimal digits of the sql server type
// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx
// for SQL_VARBINARY, SQL_VARCHAR, and SQL_WLONGVARCHAR types, see https://msdn.microsoft.com/en-CA/library/ms187993.aspx
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
_Out_ SQLSMALLINT* decimal_digits )
{
*decimal_digits = 0;
switch( sqlsrv_type.typeinfo.type ) {
case SQL_BIGINT:
*column_size = 19;
break;
case SQL_BIT:
*column_size = 1;
break;
case SQL_INTEGER:
*column_size = 10;
break;
case SQL_SMALLINT:
*column_size = 5;
break;
case SQL_TINYINT:
*column_size = 3;
break;
case SQL_GUID:
*column_size = 36;
break;
case SQL_FLOAT:
*column_size = 53;
break;
case SQL_REAL:
*column_size = 24;
break;
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
*column_size = INT_MAX;
break;
case SQL_WLONGVARCHAR:
*column_size = INT_MAX >> 1;
break;
case SQL_SS_XML:
case SQL_SS_TABLE:
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
case SQL_BINARY:
case SQL_CHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_SS_VARIANT:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*column_size = SQL_SS_LENGTH_UNLIMITED;
}
else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
}
if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
case SQL_DECIMAL:
case SQL_NUMERIC:
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
// if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision
if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
// this can represent one of three data types: smalldatetime, datetime, and datetime2
// we present the largest for the version and let SQL Server downsize it
case SQL_TYPE_TIMESTAMP:
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
break;
case SQL_SS_TIMESTAMPOFFSET:
*column_size = 34;
*decimal_digits = 7;
break;
case SQL_TYPE_DATE:
*column_size = 10;
*decimal_digits = 0;
break;
case SQL_SS_TIME2:
*column_size = 16;
*decimal_digits = 7;
break;
default:
// an invalid sql type should have already been dealt with, so we assert here.
DIE( "Trying to determine column size for an invalid type. Type should have already been verified." );
return false;
}
return true;
}
// given a SQL Server type, return a sqlsrv php type
sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string )
{
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
switch( sql_type ) {
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:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_SS_VARIANT:
if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
break;
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
break;
case SQL_FLOAT:
case SQL_REAL:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
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 (stmt->date_as_string) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
}
case SQL_SS_TABLE:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
break;
default:
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
SQLSRV_ASSERT(false, "An invalid php type was returned with (supposedly) validated sql type and column_size");
break;
}
// if an encoding hasn't been set for the statement, then use the connection's encoding
if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding();
}
return sqlsrv_phptype;
}
// determine if a query returned any rows of data. It does this by actually fetching the first row
// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful.
// The return value simply states whether or not if an error occurred during the determination.
// (All errors are posted here before returning.)
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt )
{
SQLRETURN r = SQL_SUCCESS;
if( stmt->fetch_called ) {
return;
}
// default condition
stmt->has_rows = false;
// if there are no columns then there are no rows
if( core::SQLNumResultCols( stmt ) == 0 ) {
return;
}
// if the statement is scrollable, our work is easier though less performant. We simply
// fetch the first row, and then roll the cursor back to be prior to the first row
if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) {
r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt );
// restore the cursor to its original position.
r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 );
SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning "
"of the result set." );
}
}
else {
// otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this
// flag and simply skips the first fetch, knowing it was already done. It records its own
// flags to know if it should fetch on subsequent calls.
r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt );
return;
}
}
}
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt)
{
// get the numer of columns in the result set
SQLSMALLINT num_cols = -1;
num_cols = stmt->current_meta_data.size();
bool getMetaData = false;
if (num_cols == 0) {
getMetaData = true;
if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
num_cols = core::SQLNumResultCols(stmt);
stmt->column_count = num_cols;
} else {
num_cols = stmt->column_count;
}
}
try {
if (getMetaData) {
for (int i = 0; i < num_cols; i++) {
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
core_meta_data = core_sqlsrv_field_metadata(stmt, i);
stmt->current_meta_data.push_back(core_meta_data.get());
core_meta_data.transferred();
}
}
} catch( core::CoreException& ) {
throw;
}
SQLSRV_ASSERT(stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" );
return num_cols;
}
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names )
{
void* field_value = NULL;
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
// make sure that the fetch type is legal
CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) {
throw ss::SSException();
}
// get the numer of columns in the result set and its metadata if not exists
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
// if this is the first fetch in a new result set, then get the field names and
// store them off for successive fetches.
if ((fetch_type & SQLSRV_FETCH_ASSOC) && stmt->fetch_field_names == NULL) {
SQLLEN field_name_len = 0;
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names;
field_names = static_cast<sqlsrv_fetch_field_name*>(sqlsrv_malloc(num_cols * sizeof(sqlsrv_fetch_field_name)));
for (int i = 0; i < num_cols; ++i) {
// The meta data field name is already null-terminated, and the field name len is correct.
field_name_len = stmt->current_meta_data[i]->field_name_len;
field_names[i].name = static_cast<char*>(sqlsrv_malloc(field_name_len, sizeof(char), 1));
memcpy_s((void*)field_names[i].name, (field_name_len * sizeof(char)), (void*)stmt->current_meta_data[i]->field_name, field_name_len);
field_names[i].name[field_name_len] = '\0'; // null terminate the field name after the memcpy
field_names[i].len = field_name_len; // field_name_len should not need to include the null char
}
stmt->fetch_field_names = field_names;
stmt->fetch_fields_count = num_cols;
field_names.transferred();
}
int zr = SUCCESS;
array_init(&fields);
for( int i = 0; i < num_cols; ++i ) {
SQLLEN field_len = -1;
core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/,
field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out );
zval field;
ZVAL_UNDEF( &field );
convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field );
sqlsrv_free( field_value );
if( fetch_type & SQLSRV_FETCH_NUMERIC ) {
zr = add_next_index_zval( &fields, &field );
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
throw ss::SSException();
}
}
if( fetch_type & SQLSRV_FETCH_ASSOC ) {
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt,
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) {
throw ss::SSException();
}
if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) {
add_assoc_zval(&fields, stmt->fetch_field_names[i].name, &field);
}
}
//only addref when the fetch_type is BOTH because this is the only case when fields(hashtable)
//has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because
//fields now only has 1 element pointing to field and we want the ref count to be only 1
if (fetch_type == SQLSRV_FETCH_BOTH) {
Z_TRY_ADDREF(field);
}
} //for loop
}
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index, _Out_ SQLSMALLINT& direction,
_Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits)
{
zval* var_or_val = zend_hash_index_find(param_ht, 0);
bool php_type_param_is_null = true;
bool sql_type_param_is_null = true;
// Assumption: there are more than only the variable, parse the rest of the array
zval* dir = zend_hash_index_find(param_ht, 1);
if (Z_TYPE_P(dir) != IS_NULL) {
// if param direction is specified, make sure it's valid
CHECK_CUSTOM_ERROR(Z_TYPE_P(dir) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
throw ss::SSException();
}
direction = static_cast<SQLSMALLINT>(Z_LVAL_P(dir));
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && !Z_ISREF_P(var_or_val), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) {
throw ss::SSException();
}
}
// Check if the user provides php type or sql type or both
zval* phptype_z = zend_hash_index_find(param_ht, 2);
zval* sqltype_z = zend_hash_index_find(param_ht, 3);
php_type_param_is_null = (phptype_z == NULL || Z_TYPE_P(phptype_z) == IS_NULL);
sql_type_param_is_null = (sqltype_z == NULL || Z_TYPE_P(sqltype_z) == IS_NULL);
if (php_type_param_is_null) {
// so set default for php type based on the variable
if (Z_ISREF_P(var_or_val)) {
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))];
} else {
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)];
}
} else {
CHECK_CUSTOM_ERROR(Z_TYPE_P(phptype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
throw ss::SSException();
}
sqlsrv_phptype srv_phptype;
srv_phptype.value = Z_LVAL_P(phptype_z);
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(srv_phptype), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
throw ss::SSException();
}
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
encoding = (SQLSRV_ENCODING)srv_phptype.typeinfo.encoding;
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
// by the connection
if (encoding == SQLSRV_ENCODING_DEFAULT) {
encoding = stmt->conn->encoding();
}
}
if (sql_type_param_is_null) {
// the sql type is not specified, which is required for always encrypted for non-prepared statements
CHECK_CUSTOM_ERROR(stmt->conn->ce_option.enabled && !stmt->prepared, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
throw ss::SSException();
}
} else {
CHECK_CUSTOM_ERROR(Z_TYPE_P(sqltype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
throw ss::SSException();
}
// since the user supplied this type, make sure it's valid
sqlsrv_sqltype sqlsrv_sql_type;
sqlsrv_sql_type.value = Z_LVAL_P(sqltype_z);
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_sqltype(sqlsrv_sql_type), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
throw ss::SSException();
}
bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits);
CHECK_CUSTOM_ERROR(!size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1) {
throw ss::SSException();
}
sql_type = sqlsrv_sql_type.typeinfo.type;
if (direction != SQL_PARAM_INPUT && php_type_param_is_null) {
sqlsrv_phptype srv_phptype;
srv_phptype = determine_sqlsrv_php_type(stmt, sql_type, (SQLUINTEGER)column_size, true);
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
encoding = static_cast<SQLSRV_ENCODING>(srv_phptype.typeinfo.encoding);
}
}
if (direction == SQL_PARAM_OUTPUT) {
if (php_out_type == SQLSRV_PHPTYPE_NULL || php_out_type == SQLSRV_PHPTYPE_DATETIME || php_out_type == SQLSRV_PHPTYPE_STREAM) {
THROW_CORE_ERROR(stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
}
}
return var_or_val;
}
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
{
switch( type.typeinfo.type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_TABLE:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
{
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
return true;
}
break;
}
}
return false;
}
// return if the type is a valid sql server type not including
// size, precision or scale. Use determine_precision_and_scale for that.
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type )
{
switch( sql_type.typeinfo.type ) {
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_GUID:
case SQL_FLOAT:
case SQL_REAL:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
case SQL_BINARY:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TABLE:
break;
default:
return false;
}
return true;
}
// verify an encoding given to type_and_encoding by looking through the list
// of standard encodings created at module initialization time
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding )
{
void* encoding_temp = NULL;
zend_ulong index = -1;
zend_string* key = NULL;
ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) {
if (encoding_temp) {
sqlsrv_encoding* encoding = reinterpret_cast<sqlsrv_encoding*>(encoding_temp);
encoding_temp = NULL;
if (!stricmp(encoding_string, encoding->iana)) {
phptype_encoding.typeinfo.encoding = encoding->code_page;
return true;
}
}
else {
DIE("Fatal: Error retrieving encoding from encoding hash table.");
}
} ZEND_HASH_FOREACH_END();
return false;
}
// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size
// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h).
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
{
char* size_p = NULL;
size_t size_len = 0;
int size = 0;
if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &size_p, &size_len ) == FAILURE ) {
return;
}
if (size_p) {
if (!strnicmp("max", size_p, sizeof("max") / sizeof(char))) {
size = SQLSRV_SIZE_MAX_TYPE;
}
else {
#ifndef _WIN32
errno = 0;
#else
_set_errno(0); // reset errno for atol
#endif // !_WIN32
size = atol(size_p);
if (errno != 0) {
size = SQLSRV_INVALID_SIZE;
}
}
}
else {
DIE("type_and_size_calc: size_p is null.");
}
int max_size = SQL_SERVER_MAX_FIELD_SIZE;
// size is actually the number of characters, not the number of bytes, so if they ask for a
// 2 byte per character type, then we half the maximum size allowed.
if( type == SQL_WVARCHAR || type == SQL_WCHAR ) {
max_size >>= 1;
}
if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) {
LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size );
size = SQLSRV_INVALID_SIZE;
}
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = type;
sql_type.typeinfo.size = size;
sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE;
ZVAL_LONG( return_value, sql_type.value );
}
// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the
// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h)
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
{
zend_long prec = SQLSRV_INVALID_PRECISION;
zend_long scale = SQLSRV_INVALID_SCALE;
if( zend_parse_parameters( ZEND_NUM_ARGS(), "|ll", &prec, &scale ) == FAILURE ) {
return;
}
if( prec > SQL_SERVER_MAX_PRECISION ) {
LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" );
prec = SQLSRV_INVALID_PRECISION;
}
if( prec < 0 ) {
LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" );
prec = SQLSRV_INVALID_PRECISION;
}
if( scale > prec ) {
LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" );
scale = SQLSRV_INVALID_SCALE;
}
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = type;
sql_type.typeinfo.size = prec;
sql_type.typeinfo.scale = scale;
ZVAL_LONG( return_value, sql_type.value );
}
// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters.
// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h)
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
{
SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." );
char* encoding_param;
size_t encoding_param_len = 0;
// set the default encoding values to invalid so that
// if the encoding isn't validated, it will return the invalid setting.
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = type;
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &encoding_param, &encoding_param_len ) == FAILURE ) {
ZVAL_LONG( return_value, sqlsrv_php_type.value );
}
if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type )) {
LOG( SEV_ERROR, "Invalid encoding for php type." );
}
ZVAL_LONG( return_value, sqlsrv_php_type.value );
}
}

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\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,993 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: util.cpp
//
// Contents: Utility functions used by both connection or statement functions
//
// Comments: Mostly error handling and some type handling
//
// 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_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_UTIL;
// *** internal functions ***
sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code );
void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain,
_In_ bool warning );
bool ignore_warning( _In_ char* sql_state, _In_ int native_code );
bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ logging_severity log_severity,
_In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args );
int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest );
bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z );
}
// List of all error messages
ss_error SS_ERRORS[] = {
{
SS_SQLSRV_ERROR_INVALID_OPTION,
{ IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true }
},
// no equivalent to error 2 in 2.0
// error 3 is superceded by -16
// these two share the same code since they are basically the same error.
{
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 (}}).", -4, false }
},
{
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true }
},
{
SQLSRV_ERROR_NO_DATA,
{ IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true }
},
{
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
{ IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using "
"streams.", -6, false}
},
{
SS_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.", -7, false }
},
{
SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY,
{ IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false }
},
{
SS_SQLSRV_ERROR_VAR_REQUIRED,
{ IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true }
},
{
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE,
{ IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false }
},
{
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
{ IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false }
},
{
SS_SQLSRV_ERROR_ALREADY_IN_TXN,
{ IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either "
"sqlsrv_commit or sqlsrv_rollback.", -12, false }
},
{
SS_SQLSRV_ERROR_NOT_IN_TXN,
{ IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or "
"sqlsrv_rollback.", -13, false }
},
{
SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER,
{ IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true }
},
{
SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION,
{ IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and "
"SQLSRV_PARAM_INOUT are valid values.", -15, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
{ IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
{ IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true }
},
{
SQLSRV_ERROR_FETCH_NOT_CALLED,
{ IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false }
},
{
SQLSRV_ERROR_FIELD_INDEX_ERROR,
{ IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. "
"The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less "
"than the index of a field that has already been retrieved (%2!d!).", -19, true }
},
{
SQLSRV_ERROR_DATETIME_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false }
},
// no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped
{
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.", -22, false }
},
{
SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED,
{ IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false }
},
{
SQLSRV_ERROR_ZEND_HASH,
{ IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false }
},
{
SQLSRV_ERROR_ZEND_STREAM,
{ IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false }
},
{
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false }
},
{
SQLSRV_ERROR_STREAM_CREATE,
{ IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false }
},
{
SQLSRV_ERROR_NO_FIELDS,
{ IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false }
},
{
SS_SQLSRV_ERROR_ZEND_BAD_CLASS,
{ IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true }
},
{
SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED,
{ IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true }
},
{
SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION,
{ IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_KEY,
{ IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true }
},
// these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1
{
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true }
},
{
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
{ IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant."
" For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. "
"If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true }
},
{
SQLSRV_ERROR_INVALID_TYPE,
{ IMSSP, (SQLCHAR*)"Invalid type", -35, false }
},
// 36-38 have no equivalent 2.0 errors
{
SS_SQLSRV_ERROR_REGISTER_RESOURCE,
{ IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true }
},
{
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true }
},
{
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true }
},
{
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true }
},
{
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.", -44, false }
},
{
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.", -45, false }
},
{
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true }
},
{
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true }
},
{
SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING,
{ IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true }
},
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver for SQL Server. "
"Access the following URL to download the ODBC Driver for SQL Server for %1!s!: "
"https://go.microsoft.com/fwlink/?LinkId=163712", -49, true }
},
{
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false }
},
{
SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false }
},
// new error for 2.0, used here since 1.1 didn't have a -52
{
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true }
},
{
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE,
{ IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. "
"Please use one of the SQLSRV_SCROLL constants.", -53, false }
},
{
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', "
"'keyset', 'forward', or 'buffered'.", -54, false }
},
{
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
{ IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true }
},
{
SS_SQLSRV_ERROR_PARAM_INVALID_INDEX,
{ IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
{ IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true }
},
{
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true }
},
{
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
{ IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false }
},
{
SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF,
{ IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). "
"Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value."
, -61, true }
},
{
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED,
{ IMSSP, (SQLCHAR*)"Must specify the SQL type for each parameter in a parameterized query when using sqlsrv_query in a column encryption enabled connection.", -63, false }
},
// internal warning definitions
{
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
{ SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, 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!.", -105, true }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -106, true }
},
{
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
{ IMSSP, (SQLCHAR*) "The specified ODBC Driver is not found.", -107, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
},
{
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"Error converting a double (value out of range) to an integer.", -109, false }
},
{
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the KeyStoreAuthentication keyword. Only KeyVaultPassword or KeyVaultClientSecret is allowed.", -110, false }
},
{
SQLSRV_ERROR_AKV_AUTH_MISSING,
{ IMSSP, (SQLCHAR*) "The authentication method for Azure Key Vault is missing. KeyStoreAuthentication must be set to KeyVaultPassword or KeyVaultClientSecret.", -111, false }
},
{
SQLSRV_ERROR_AKV_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The username or client Id for Azure Key Vault is missing.", -112, false }
},
{
SQLSRV_ERROR_AKV_SECRET_MISSING,
{ IMSSP, (SQLCHAR*) "The password or client secret for Azure Key Vault is missing.", -113, false }
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -114, false}
},
{
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.", -115, false}
},
{
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, 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.", -117, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
{ IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -119, 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.", -120, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -121, true}
},
{
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!", -122, true }
},
{
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -123, true }
},
{
SQLSRV_ERROR_TVP_FETCH_METADATA,
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -124, true }
},
{
SQLSRV_ERROR_TVP_INVALID_INPUTS,
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -125, 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!", -126, 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!", -127, true }
},
{
SQLSRV_ERROR_TVP_STRING_KEYS,
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -128, true }
},
{
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -129, true }
},
{
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -130, false }
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }
};
// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool ss_severity_check(_In_ unsigned int severity)
{
return ((severity & SQLSRV_G(log_severity)) && (SQLSRV_G(current_subsystem) & SQLSRV_G(log_subsystems)));
}
bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args )
{
logging_severity severity = SEV_ERROR;
if( warning && !SQLSRV_G( warnings_return_as_errors )) {
severity = SEV_WARNING;
}
return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning,
print_args );
}
// sqlsrv_errors( [int $errorsAndOrWarnings] )
//
// Returns extended error and/or warning information about the last sqlsrv
// operation performed.
//
// The sqlsrv_errors function can return error and/or warning information by
// calling it with one of the following parameter values below.
//
// Parameters
//
// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can
// take one of the values listed:
//
// SQLSRV_ERR_ALL
// Errors and warnings generated on the last sqlsrv function call are returned.
// SQLSRV_ERR_ERRORS
// Errors generated on the last sqlsrv function call are returned.
// SQLSRV_ERR_WARNINGS
// Warnings generated on the last sqlsrv function call are returned.
//
// If no parameter value is supplied, SQLSRV_ERR_ALL is the default
//
// Return Value
// An array of arrays, or null. An example of an error returned:
// Array
// (
// [0] => Array
// (
// [0] => HYT00
// [SQLSTATE] => HYT00
// [1] => 0
// [code] => 0
// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired
// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired
// )
// )
PHP_FUNCTION( sqlsrv_errors )
{
// SQLSRV_UNUSED( execute_data );
zend_long flags = SQLSRV_ERR_ALL;
LOG_FUNCTION( "sqlsrv_errors" );
if(( zend_parse_parameters( ZEND_NUM_ARGS(), "|l", &flags ) == FAILURE ) ||
( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) {
LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ );
RETURN_FALSE;
}
zval err_z;
ZVAL_UNDEF(&err_z);
array_init(&err_z);
if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) {
if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) )) {
zval_ptr_dtor(&err_z);
RETURN_FALSE;
}
}
if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) {
if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) )) {
zval_ptr_dtor(&err_z);
RETURN_FALSE;
}
}
if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) {
zval_ptr_dtor(&err_z);
RETURN_NULL();
}
RETURN_ZVAL( &err_z, 1, 1 );
}
// sqlsrv_configure( string $setting, mixed $value )
//
// Changes the settings for error handling and logging options.
//
// Parameters
// $setting: The name of the setting to be configured. The possible implemented values are
// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity".
//
// $value: The value to be applied to the setting specified in the $setting
// parameter. See MSDN or the MINIT function for possible values.
//
// Return Value
// If sqlsrv_configure is called with an unsupported setting or value, the
// function returns false. Otherwise, the function returns true.
PHP_FUNCTION( sqlsrv_configure )
{
// SQLSRV_UNUSED( execute_data );
LOG_FUNCTION( "sqlsrv_configure" );
char* option;
size_t option_len;
zval* value_z;
sqlsrv_context_auto_ptr error_ctx;
RETVAL_FALSE;
reset_errors();
try {
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "sz", &option, &option_len, &value_z );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
// WarningsReturnAsErrors
SQLSRV_ASSERT( option[option_len] == '\0', "sqlsrv_configure: option was not null terminated." );
if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) {
SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false;
LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off");
RETURN_TRUE;
}
// LogSeverity
else if( !stricmp( option, INI_LOG_SEVERITY )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
zend_long severity_mask = Z_LVAL_P( value_z );
// make sure they can't use 0 to shut off the masking in the severity
if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) {
RETURN_FALSE;
}
SQLSRV_G( log_severity ) = static_cast<logging_severity>( severity_mask );
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity ));
RETURN_TRUE;
}
// LogSubsystems
else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
zend_long subsystem_mask = Z_LVAL_P( value_z );
if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) {
RETURN_FALSE;
}
SQLSRV_G( log_subsystems ) = static_cast<logging_subsystems>( subsystem_mask );
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems ));
RETURN_TRUE;
}
else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) {
throw ss::SSException();
}
zend_long buffered_query_limit = Z_LVAL_P( value_z );
CHECK_CUSTOM_ERROR( buffered_query_limit <= 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) {
throw ss::SSException();
}
SQLSRV_G( buffered_query_limit ) = buffered_query_limit;
LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit ));
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_configure: Unknown exception caught." );
}
}
// sqlsrv_get_config( string $setting )
//
// Returns the current value of the specified configuration setting.
//
// Parameters
// $setting: The configuration setting for which the value is returned. For a
// list of configurable settings, see sqlsrv_configure.
//
// Return Value
// The value of the setting specified by the $setting parameter. If an invalid
// setting is specified, false is returned and an error is added to the error
// collection. Because false is a valid value for WarningsReturnAsErrors, to
// really determine if an error occurred, call sqlsrv_errors.
PHP_FUNCTION( sqlsrv_get_config )
{
// SQLSRV_UNUSED( execute_data );
char* option = NULL;
size_t option_len;
sqlsrv_context_auto_ptr error_ctx;
LOG_FUNCTION( "sqlsrv_get_config" );
reset_errors();
try {
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "s", &option, &option_len );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
SQLSRV_ASSERT( option != NULL, "sqlsrv_get_config: option was null." );
if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) {
ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors ));
return;
}
else if( !stricmp( option, INI_LOG_SEVERITY )) {
ZVAL_LONG( return_value, SQLSRV_G( log_severity ));
return;
}
else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) {
ZVAL_LONG( return_value, SQLSRV_G( log_subsystems ));
return;
}
else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) {
ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit ));
return;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_get_config: Unknown exception caught." );
}
}
namespace {
sqlsrv_error_const* get_error_message( _In_ 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_ss_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 copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain,
_In_ bool warning )
{
array_init(error_z);
// sqlstate
zval temp;
ZVAL_UNDEF(&temp);
core::sqlsrv_zval_stringl( &temp, reinterpret_cast<char*>( error->sqlstate ), SQL_SQLSTATE_SIZE );
Z_TRY_ADDREF_P( &temp );
if( add_next_index_zval( error_z, &temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
add_assoc_zval(error_z, "SQLSTATE", &temp);
// native_code
if( add_next_index_long( error_z, error->native_code ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
add_assoc_long(error_z, "code", error->native_code);
// native_message
ZVAL_UNDEF(&temp);
ZVAL_STRING( &temp, reinterpret_cast<char*>( error->native_message ) );
Z_TRY_ADDREF_P(&temp);
if( add_next_index_zval( error_z, &temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
add_assoc_zval(error_z, "message", &temp);
// If it is an error or if warning_return_as_errors is true than
// add the error or warning to the reported_chain.
if( !warning || SQLSRV_G( warnings_return_as_errors ) )
{
// if the warning is part of the ignored warning list than
// add to the ignored chain if the ignored chain is not null.
if( warning && ignore_warning( reinterpret_cast<char*>(error->sqlstate), error->native_code ) &&
ignored_chain != NULL ) {
if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
else {
// It is either an error or a warning which should not be ignored.
if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
}
else
{
// It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list
if( ignored_chain != NULL ) {
if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
}
}
bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ logging_severity log_severity,
_In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args )
{
bool result = true;
bool errors_ignored = false;
size_t prev_reported_cnt = 0;
bool reported_chain_was_null = false;
bool ignored_chain_was_null = false;
zval error_z;
ZVAL_UNDEF(&error_z);
sqlsrv_error_auto_ptr error;
// array of reported errors
if( Z_TYPE_P( reported_chain ) == IS_NULL ) {
reported_chain_was_null = true;
array_init(reported_chain);
}
else {
prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain ));
}
// array of ignored errors
if( ignored_chain != NULL ) {
if( Z_TYPE_P( ignored_chain ) == IS_NULL ) {
ignored_chain_was_null = true;
array_init( ignored_chain );
}
}
if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity, print_args );
copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning );
}
SQLSMALLINT record_number = 0;
do {
result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity );
if( result ) {
copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning );
}
} while( result );
// If it were a warning, we report that warnings where ignored except if warnings_return_as_errors
// was true and we added some warnings to the reported_chain.
if( warning ) {
errors_ignored = true;
if( SQLSRV_G( warnings_return_as_errors ) ) {
if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) {
// We actually added some errors
errors_ignored = false;
}
}
}
// if the error array came in as NULL and didn't have anything added to it, return it as NULL
if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) {
zend_hash_destroy( Z_ARRVAL_P( reported_chain ));
FREE_HASHTABLE( Z_ARRVAL_P( reported_chain ));
ZVAL_NULL( reported_chain );
}
if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) {
zend_hash_destroy( Z_ARRVAL_P( ignored_chain ));
FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain ));
ZVAL_NULL( ignored_chain );
}
// If it was an error instead of a warning than we always return errors_ignored = false.
return errors_ignored;
}
// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true
// see RINIT in init.cpp for information about which errors are ignored.
bool ignore_warning( _In_ char* sql_state, _In_ int native_code )
{
zend_ulong index = -1;
zend_string* key = NULL;
void* error_temp = NULL;
ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) {
sqlsrv_error* error = static_cast<sqlsrv_error*>( error_temp );
if (NULL == error) {
return false;
}
if( !strncmp( reinterpret_cast<char*>( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) &&
( error->native_code == native_code || error->native_code == -1 )) {
return true;
}
} ZEND_HASH_FOREACH_END();
return false;
}
int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest )
{
zval_ptr_dtor( dest );
return ZEND_HASH_APPLY_REMOVE;
}
// sqlsrv_merge_zend_hash
// merge a source hash into a dest hash table and return any errors.
bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z )
{
if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" );
if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" );
if( Z_TYPE_P( src_z ) == IS_NULL ) {
return true;
}
HashTable* src_ht = Z_ARRVAL_P( src_z );
zend_ulong index = -1;
zend_string* key = NULL;
zval* value_z = NULL;
ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) {
if ( !value_z ) {
zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor );
return false;
}
int result = add_next_index_zval( dest_z, value_z );
if( result == FAILURE ) {
zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor );
return false;
}
Z_TRY_ADDREF_P( value_z );
} ZEND_HASH_FOREACH_END();
return true;
}
} // namespace

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