From cdc107b7b1ed3c42db4416371aa0790f722dc10b Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 21 Feb 2019 17:39:02 -0800 Subject: [PATCH 1/7] Master (#936) 5.6.0 RTW --- CHANGELOG.md | 46 ++ LICENSE | 2 +- Linux-mac-install.md | 84 ++- README.md | 7 +- buildscripts/builddrivers.py | 53 +- buildscripts/buildtools.py | 3 +- buildscripts/indexsymbols.py | 145 +++++ source/pdo_sqlsrv/config.m4 | 3 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 8 +- source/pdo_sqlsrv/pdo_init.cpp | 30 +- source/pdo_sqlsrv/pdo_parser.cpp | 8 +- source/pdo_sqlsrv/pdo_stmt.cpp | 8 +- source/pdo_sqlsrv/pdo_util.cpp | 14 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 378 +----------- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 409 +++++++++++++ source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 49 +- source/shared/core_init.cpp | 19 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 4 +- source/shared/core_stmt.cpp | 13 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 10 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 6 +- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 7 +- source/sqlsrv/config.w32 | 6 +- source/sqlsrv/conn.cpp | 8 +- source/sqlsrv/init.cpp | 30 +- source/sqlsrv/php_sqlsrv.h | 540 +----------------- source/sqlsrv/php_sqlsrv_int.h | 468 +++++++++++++++ source/sqlsrv/stmt.cpp | 9 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 14 +- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 27 + .../pdo_sqlsrv/PDO81_MemoryCheck.phpt | 2 +- ...ment_bindParam_output_emulate_prepare.phpt | 1 + ...28_setAttribute_clientbuffermaxkbsize.phpt | 32 +- ...etConnAttribute_clientbuffermaxkbsize.phpt | 94 +++ .../pdo_sqlsrv/pdo_574_next_rowset.phpt | 1 + .../pdo_900_output_param_memory_data.phpt | 80 +++ .../pdo_azure_ad_authentication.phpt | 69 +-- .../pdo_azure_ad_managed_identity.phpt | 114 ++++ test/functional/pdo_sqlsrv/pdo_passwords.phpt | 57 -- .../pdo_sqlsrv/pdo_prepare_attribute.phpt | 1 + .../functional/pdo_sqlsrv/skipif_azure_dw.inc | 10 + test/functional/setup/168256.sql | 3 - test/functional/setup/cd_info.sql | 5 +- test/functional/setup/cleanup_dbs.py | 7 +- test/functional/setup/create_db.sql | 18 +- test/functional/setup/create_logins_azure.sql | 18 - test/functional/setup/create_users_azure.sql | 21 - test/functional/setup/drop_db.sql | 9 +- test/functional/setup/exec_sql_scripts.py | 39 +- test/functional/setup/setup_dbs.py | 58 +- test/functional/setup/test_password.sql | 31 - test/functional/setup/test_types.sql | 31 +- test/functional/setup/tracks.sql | 3 - test/functional/sqlsrv/0013.phpt | 2 +- test/functional/sqlsrv/0022.phpt | 2 +- test/functional/sqlsrv/53_0021.phpt | 2 +- test/functional/sqlsrv/MsCommon.inc | 22 + test/functional/sqlsrv/TC81_MemoryCheck.phpt | 4 +- .../sqlsrv/bugfix_dataCorruption.phpt | 2 +- test/functional/sqlsrv/fix_test_168256-2.phpt | 2 +- test/functional/sqlsrv/fix_test_168256.phpt | 2 +- test/functional/sqlsrv/fix_test_182741.phpt | 1 + test/functional/sqlsrv/skipif_azure_dw.inc | 12 + .../sqlsrv/sqlsrv_574_next_result.phpt | 1 + .../sqlsrv_900_output_param_memory_data.phpt | 77 +++ .../sqlsrv_azure_ad_authentication.phpt | 73 +-- .../sqlsrv_azure_ad_managed_identity.phpt | 88 +++ .../functional/sqlsrv/sqlsrv_data_to_str.phpt | 2 +- test/functional/sqlsrv/sqlsrv_get_field.phpt | 2 +- test/functional/sqlsrv/sqlsrv_metadata.phpt | 2 +- test/functional/sqlsrv/sqlsrv_readStream.phpt | 2 +- .../sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt | 3 +- .../srv_228_sqlsrv_clientbuffermaxkbsize.phpt | 45 +- ...8_sqlsrv_clientbuffermaxkbsize_option.phpt | 109 ++++ .../sqlsrv/test_closeConnection.phpt | 2 +- test/functional/sqlsrv/test_fetch.phpt | 2 +- test/functional/sqlsrv/test_fetch2.phpt | 2 +- test/functional/sqlsrv/test_insert_null.phpt | 2 +- .../sqlsrv/test_insert_nullStr.phpt | Bin 2663 -> 2672 bytes test/functional/sqlsrv/test_largeData.phpt | 2 +- test/functional/sqlsrv/test_newError_msg.phpt | 2 +- .../sqlsrv/test_non_alpha_password.phpt | 61 -- .../sqlsrv/test_sqlsrv_phptype_stream.phpt | Bin 10374 -> 10383 bytes test/functional/sqlsrv/test_stream.phpt | 2 +- .../sqlsrv/test_warning_errors2.phpt | 2 +- 107 files changed, 2215 insertions(+), 1473 deletions(-) create mode 100644 buildscripts/indexsymbols.py create mode 100644 source/pdo_sqlsrv/php_pdo_sqlsrv_int.h create mode 100644 source/sqlsrv/php_sqlsrv_int.h create mode 100644 test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_900_output_param_memory_data.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt delete mode 100644 test/functional/pdo_sqlsrv/pdo_passwords.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_azure_dw.inc delete mode 100644 test/functional/setup/create_logins_azure.sql delete mode 100644 test/functional/setup/create_users_azure.sql delete mode 100644 test/functional/setup/test_password.sql create mode 100644 test/functional/sqlsrv/skipif_azure_dw.inc create mode 100644 test/functional/sqlsrv/sqlsrv_900_output_param_memory_data.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt create mode 100644 test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt delete mode 100644 test/functional/sqlsrv/test_non_alpha_password.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 83a46a7fa..946ce626a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 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: diff --git a/LICENSE b/LICENSE index 13fab6115..6fa366d5d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright(c) 2018 Microsoft Corporation +Copyright(c) 2019 Microsoft Corporation All rights reserved. MIT License diff --git a/Linux-mac-install.md b/Linux-mac-install.md index c83bec3fb..2a7357f47 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,37 +1,32 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md##loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12 and 15, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver##loading-the-driver-at-php-startup). -These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. +These instructions install PHP 7.3 by default. Note that some supported Linux distros default to PHP 7.0 or earlier, which is not supported for the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.1 or 7.2 instead. ## Contents of this page: - [Installing the drivers on Ubuntu 16.04, 18.04, and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) -- [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) +- [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) - [Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) ## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10 > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands. +> To install PHP 7.1 or 7.2, replace 7.3 with 7.1 or 7.2 in the following commands. ### Step 1. Install PHP ``` sudo su add-apt-repository ppa:ondrej/php -y apt-get update -apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated +apt-get install php7.3 php7.3-dev php7.3-xml -y --allow-unauthenticated ``` ### Step 2. Install prerequisites Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). -For Ubuntu 18.10, follow the above steps for Ubuntu 18.04 except replace `18.04` by `18.10` and download ODBC 17.3 preview [here](https://www.microsoft.com/download/details.aspx?id=57341). - ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -46,9 +41,9 @@ sudo su apt-get install libapache2-mod-php7.2 apache2 a2dismod mpm_event a2enmod mpm_prefork -a2enmod php7.2 -echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini -echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini +a2enmod php7.3 +echo "extension=pdo_sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/30-pdo_sqlsrv.ini +echo "extension=sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/20-sqlsrv.ini exit ``` ### Step 5. Restart Apache and test the sample script @@ -60,7 +55,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Red Hat 7 > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `remi-php72` with `remi-php70`, `remi-php71`, or `remi-php73` respectively in the following commands. +> To install PHP 7.1 or 7.2, replace remi-php73 with remi-php71 or remi-php72 respectively in the following commands. ### Step 1. Install PHP @@ -71,23 +66,20 @@ wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm subscription-manager repos --enable=rhel-7-server-optional-rpms yum install yum-utils -yum-config-manager --enable remi-php72 +yum-config-manager --enable remi-php73 yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc ``` ### Step 2. Install prerequisites Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). -Compiling the PHP drivers with PECL with PHP 7.2 requires a more recent GCC than the default: +Compiling the PHP drivers with PECL with PHP 7.2 or 7.3 requires a more recent GCC than the default: ``` sudo yum-config-manager --enable rhel-server-rhscl-7-rpms sudo yum install devtoolset-7 scl enable devtoolset-7 bash ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -99,8 +91,8 @@ exit An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` pecl download sqlsrv -tar xvzf sqlsrv-5.3.0.tgz -cd sqlsrv-5.3.0/ +tar xvzf sqlsrv-5.6.0.tgz +cd sqlsrv-5.6.0/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -127,7 +119,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Debian 8 and 9 > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands. +> To install PHP 7.1 or 7.2, replace 7.3 in the following commands with 7.1 or 7.2. ### Step 1. Install PHP ``` @@ -136,7 +128,7 @@ apt-get install curl apt-transport-https wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list apt-get update -apt-get install -y php7.2 php7.2-dev php7.2-xml +apt-get install -y php7.3 php7.3-dev php7.3-xml ``` ### Step 2. Install prerequisites Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). @@ -149,9 +141,6 @@ locale-gen ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -163,12 +152,12 @@ exit ### Step 4. Install Apache and configure driver loading ``` sudo su -apt-get install libapache2-mod-php7.2 apache2 +apt-get install libapache2-mod-php7.3 apache2 a2dismod mpm_event a2enmod mpm_prefork -a2enmod php7.2 -echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini -echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini +a2enmod php7.3 +echo "extension=pdo_sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/30-pdo_sqlsrv.ini +echo "extension=sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/20-sqlsrv.ini ``` ### Step 5. Restart Apache and test the sample script ``` @@ -176,27 +165,32 @@ 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 12 +## Installing the drivers on Suse 12 and 15 > [!NOTE] -> To install PHP 7.0 or 7.1, replace the repository URL below with one of the following URLs: -`https://download.opensuse.org/repositories/devel:languages:php:php70/SLE_12_SP3/devel:languages:php:php70.repo` -`https://download.opensuse.org/repositories/devel:languages:php:php71/SLE_12_SP3/devel:languages:php:php71.repo` +> In the following instructions, replace with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1, and similarly for other versions. Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. + +> [!NOTE] +> Packages for PHP 7.3 are not available for Suse 12. +> To install PHP 7.1, replace the repository URL below with the following URL: + `https://download.opensuse.org/repositories/devel:/languages:/php:/php71//devel:languages:php:php71.repo`. +> To install PHP 7.2, replace the repository URL below with the following URL: + `https://download.opensuse.org/repositories/devel:/languages:/php:/php72//devel:languages:php:php72.repo`. ### Step 1. Install PHP ``` sudo su -zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php/SLE_12_SP3/devel:languages:php.repo +zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php//devel:languages:php.repo zypper --gpg-auto-import-keys refresh -zypper -n install php7 php7-pear php7-devel +zypper -n install php7 php7-pear php7-devel php7-openssl ``` ### Step 2. Install prerequisites -Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Suse by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). ### Step 3. Install the PHP drivers for Microsoft SQL Server - > [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. +> If you get an error message saying `Connection to 'pecl.php.net:443' failed: Unable to find the socket transport "ssl"`, edit the pecl script at /usr/bin/pecl and remove the `-n` switch in the last line. This switch prevents PECL from loading ini files when PHP is called, which prevents the OpenSSL extension from loading. + ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -228,18 +222,18 @@ If you do not already have it, install brew as follows: ``` > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `php@7.2` with `php@7.0`, `php@7.1`, or `php@7.3` respectively in the following commands. +> To install PHP 7.1 or 7.2, replace php@7.3 with php@7.1 or php@7.2 respectively in the following commands. ### Step 1. Install PHP ``` brew tap brew tap homebrew/core -brew install php@7.2 +brew install php@7.3 ``` PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following: ``` -brew link --force --overwrite php@7.2 +brew link --force --overwrite php@7.3 ``` ### Step 2. Install prerequisites @@ -251,9 +245,6 @@ brew install autoconf automake libtool ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -329,5 +320,4 @@ function formatErrors($errors) } ?> ``` -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. - +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. \ No newline at end of file diff --git a/README.md b/README.md index bd2b2349b..84952fe35 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ On the server side, Microsoft SQL Server 2008 R2 and above on Windows are suppor The drivers are distributed as pre-compiled extensions for PHP found on the [releases page](https://github.com/Microsoft/msphpsql/releases). They are available in thread-safe and non thread-safe versions, and in 32-bit and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. -If you choose to build the drivers, you must be able to build PHP 7 without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. +If you choose to build the drivers, you must be able to build PHP 7.* without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. @@ -88,6 +88,7 @@ The version number may have trailing pre-release version identifiers to indicate ## Future Plans - Expand SQL Server 2016 feature support (example: Azure Active Directory) - Add more verification/fundamental tests +- Improve performance - Bug fixes ## Guidelines for Reporting Issues @@ -99,14 +100,14 @@ We appreciate you taking the time to test the driver, provide feedback and repor Thank you! -## FAQs +## 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:** On July 20, 2018 we released the production release version 5.3.0 of our PHP Driver. We will continue working on our future plans and releasing previews of upcoming releases. +**A:** We will continue working on our future plans and releasing previews of upcoming [releases](https://github.com/Microsoft/msphpsql/releases) **Q:** Is Microsoft taking pull requests for this project? diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index e9ac02dcd..ff301cc53 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -24,7 +24,9 @@ import shutil import os.path import argparse +import subprocess from buildtools import BuildUtil +from indexsymbols import * class BuildDriver(object): """Build sqlsrv and/or pdo_sqlsrv drivers with PHP source with the following properties: @@ -38,6 +40,8 @@ class BuildDriver(object): 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 + srctool_path # path to source indexing tools (empty string by default) + tag_version # tag version for source indexing (empty string by default) """ def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, path, testing, no_rename): @@ -49,6 +53,8 @@ def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, pa self.testing = testing self.rebuild = False self.make_clean = False + self.srctool_path = '' + self.tag_version = '' def show_config(self): print() @@ -112,6 +118,34 @@ def get_local_source(self, source_path): print("The path provided is invalid. Please re-enter.") return source + def index_all_symbols(self, ext_dir, srctool_path, tag_version): + """This takes care of indexing all the symbols + + :param ext_dir: the directory where we can find the built extension(s) + :param srctool_path: the path to the tools for source indexing + :param tag_version: tag version for source indexing + :outcome: all symbols will be source indexed + """ + work_dir = os.path.dirname(os.path.realpath(__file__)) + os.chdir(srctool_path) + + if self.util.driver == 'all': + driver = 'sqlsrv' + pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) + print('Indexing this symbol: ', pdbfile) + run_indexing_tools(pdbfile, driver, tag_version) + driver = 'pdo_sqlsrv' + pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) + print('Indexing this symbol: ', pdbfile) + run_indexing_tools(pdbfile, driver, tag_version) + else: + driver = self.util.driver + pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) + print('Indexing this symbol: ', pdbfile) + run_indexing_tools(pdbfile, driver, tag_version) + + os.chdir(work_dir) + 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. @@ -151,6 +185,12 @@ def build_extensions(self, root_dir, logfile): # ext_dir is the directory where we can find the built extension(s) ext_dir = self.util.build_drivers(self.make_clean, dest, logfile) + # Do source indexing only if the tag and tools path are both specified + if self.tag_version is not '' and self.srctool_path is not '': + print('Source indexing begins...') + self.index_all_symbols(ext_dir, self.srctool_path, self.tag_version) + print('Source indexing done') + # 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) @@ -172,9 +212,12 @@ def build_extensions(self, root_dir, logfile): return ext_dir - def build(self): + def build(self, srctool_path, tag_version): """This is the main entry point of building drivers for PHP. For development, this will loop till the user decides to quit. + + :param srctool_path: the path to the tools for source indexing + :param tag_version: tag version for source indexing """ self.show_config() @@ -191,6 +234,10 @@ def build(self): logfile = self.util.get_logfile_name() + # Save source indexing details + self.srctool_path = srctool_path + self.tag_version = tag_version + try: ext_dir = self.build_extensions(root_dir, logfile) print('Build Completed') @@ -244,6 +291,8 @@ def validate_input(question, values): 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)") + parser.add_argument('--SRCIDX_PATH', default='', help="the path to the tools for source indexing (default: '')") + parser.add_argument('--TAG_VERSION', default='', help="the tag version for source indexing (default: '')") args = parser.parse_args() @@ -305,4 +354,4 @@ def validate_input(question, values): path, testing, no_rename) - builder.build() + builder.build(args.SRCIDX_PATH, args.TAG_VERSION) diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 79b36923a..59f8b8f55 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -336,7 +336,6 @@ def build_drivers(self, make_clean = False, dest = None, log_file = None): 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': @@ -402,6 +401,7 @@ def build_drivers(self, make_clean = False, dest = None, log_file = None): # 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): @@ -454,7 +454,6 @@ def copy_binaries(self, sdk_dir, copy_to_ext): shutil.copy(os.path.join(phpsrc, 'php.ini-production'), php_ini_file) # Copy run-tests.php as well - phpsrc = self.phpsrc_root(sdk_dir) shutil.copy(os.path.join(phpsrc, 'run-tests.php'), build_dir) print('Copying the binaries from', build_dir) diff --git a/buildscripts/indexsymbols.py b/buildscripts/indexsymbols.py new file mode 100644 index 000000000..bcf6ffbd3 --- /dev/null +++ b/buildscripts/indexsymbols.py @@ -0,0 +1,145 @@ +#!/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 "$r=New-Object -ComObject Msxml2.XMLHTTP; $r.open('GET', '%SRCURL%', $false); $r.send(); [io.file]::WriteAllBytes('%SRCSRVTRG%', $r.responseBody)" + 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('\"$r=New-Object -ComObject Msxml2.XMLHTTP; ') + f.write('$r.open(\'GET\', \'%SRCURL%\', $false); ') + f.write('$r.send(); [io.file]::WriteAllBytes(\'%SRCSRVTRG%\', $r.responseBody)\"' + 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 | find "\" | 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: -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) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 82aa69e20..68dbea0e3 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ 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.5 for PHP for SQL Server +dnl Microsoft Drivers 5.6 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License @@ -95,4 +95,3 @@ if test "$PHP_PDO_SQLSRV" != "no"; then PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo) PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) fi - diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 143627483..95bff567b 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index d1f76e495..669bc62b5 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" #include #include diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 6b7b54e39..bee4cfc52 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -17,12 +17,18 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" +#ifdef COMPILE_DL_PDO_SQLSRV #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE(); #endif ZEND_GET_MODULE(g_pdo_sqlsrv) +#endif extern "C" { @@ -308,3 +314,23 @@ namespace { { NULL , 0 } // terminate the table }; } + +// DllMain for the extension. +#ifdef _WIN32 +// Only needed if extension is built shared +#ifdef COMPILE_DL_PDO_SQLSRV +BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, LPVOID ) +{ + switch( fdwReason ) { + case DLL_PROCESS_ATTACH: + // store the module handle for use by client_info and server_info + g_sqlsrv_hmodule = hinstDLL; + break; + default: + break; + } + + return TRUE; +} +#endif +#endif diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 984f983c5..edd333e46 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -19,7 +19,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" // Constructor conn_string_parser:: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 3f7e72b8b..cd8c697f1 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" // *** internal variables and constants *** diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 91efcb902..e399fda11 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" #include "zend_exceptions.h" @@ -379,7 +383,7 @@ pdo_error PDO_ERRORS[] = { }, { PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false } + { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.", -73, false } }, { SQLSRV_ERROR_CE_DRIVER_REQUIRED, @@ -441,6 +445,10 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_INVALID_DECIMAL_PLACES, { IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false} }, + { + SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, + { IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -93, false} + }, { UINT_MAX, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index f31da7c70..3e013f95c 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -20,55 +20,12 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "core_sqlsrv.h" -#include "version.h" - -extern "C" { - -#include "pdo/php_pdo.h" -#include "pdo/php_pdo_driver.h" - -} - -#include -#include - - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// sqlsrv driver specific PDO attributes -enum PDO_SQLSRV_ATTR { - - // The custom attributes for this driver: - SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, - SQLSRV_ATTR_QUERY_TIMEOUT, - SQLSRV_ATTR_DIRECT_QUERY, - SQLSRV_ATTR_CURSOR_SCROLL_TYPE, - SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, - SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, - SQLSRV_ATTR_FETCHES_DATETIME_TYPE, - SQLSRV_ATTR_FORMAT_DECIMALS, - SQLSRV_ATTR_DECIMAL_PLACES -}; - -// valid set of values for TransactionIsolation connection option -namespace PDOTxnIsolationValues { - - const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; - const char READ_COMMITTED[] = "READ_COMMITTED"; - const char REPEATABLE_READ[] = "REPEATABLE_READ"; - const char SERIALIZABLE[] = "SERIALIZABLE"; - const char SNAPSHOT[] = "SNAPSHOT"; -} +#include "php.h" //********************************************************************************************************************************* // Global variables //********************************************************************************************************************************* -extern "C" { - // request level variables ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) @@ -79,32 +36,6 @@ ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); -} - -// macros used to access the global variables. Use these to make global variable access agnostic to threads -#ifdef ZTS -#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) -#else -#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" -#define INI_PDO_SQLSRV_LOG "log_severity" -#define INI_PREFIX "pdo_sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, - zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, - client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) -PHP_INI_END() - -// henv context for creating connections -extern sqlsrv_context* g_pdo_henv_cp; -extern sqlsrv_context* g_pdo_henv_ncp; - //********************************************************************************************************************************* // Initialization @@ -128,309 +59,4 @@ PHP_MINFO_FUNCTION(pdo_sqlsrv); extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP -// Basic string parser -class string_parser -{ - protected: - const char* orig_str; - sqlsrv_context* ctx; - int len; - int pos; - unsigned int current_key; - HashTable* element_ht; - inline bool next(void); - inline bool is_eos(void); - inline bool is_white_space( _In_ char c ); - bool discard_white_spaces(void); - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ); -}; - -//********************************************************************************************************************************* -// PDO DSN Parser -//********************************************************************************************************************************* - -// Parser class used to parse DSN connection string. -class conn_string_parser : private string_parser -{ - enum States - { - FirstKeyValuePair, - Key, - Value, - ValueContent1, - ValueContent2, - RCBEncountered, - NextKeyValuePair, - }; - - private: - const char* current_key_name; - int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); - void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC); - - protected: - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); - - public: - conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); - void parse_conn_string( TSRMLS_D ); -}; - -//********************************************************************************************************************************* -// PDO Query Parser -//********************************************************************************************************************************* - -// Parser class used to parse DSN named placeholders. -class sql_string_parser : private string_parser -{ - private: - bool is_placeholder_char(char); - public: - void add_key_int_value_pair( _In_ unsigned int value TSRMLS_DC ); - sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht); - void parse_sql_string(TSRMLS_D); -}; - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* -extern const connection_option PDO_CONN_OPTS[]; - -int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC); - -// a core layer pdo dbh object. This object inherits and overrides the statement factory -struct pdo_sqlsrv_dbh : public sqlsrv_conn { - - zval* stmts; - bool direct_query; - long query_timeout; - zend_long client_buffer_max_size; - bool fetch_numeric; - bool fetch_datetime; - bool format_decimals; - short decimal_places; - - pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); -}; - - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_encoding : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_pdo_scrollable : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_direct_query : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_cursor_scroll_type : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_emulate_prepares : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_fetch_numeric : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_fetch_datetime : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; - -// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary -struct pdo_sqlsrv_stmt : public sqlsrv_stmt { - - pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), - direct_query( false ), - direct_query_subst_string( NULL ), - direct_query_subst_string_len( 0 ), - placeholders(NULL), - bound_column_param_types( NULL ), - fetch_numeric( false ), - fetch_datetime( false ) - { - pdo_sqlsrv_dbh* db = static_cast( c ); - direct_query = db->direct_query; - fetch_numeric = db->fetch_numeric; - fetch_datetime = db->fetch_datetime; - format_decimals = db->format_decimals; - decimal_places = db->decimal_places; - } - - virtual ~pdo_sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types - virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); - - bool direct_query; // flag set if the query should be executed directly or prepared - const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters - size_t direct_query_subst_string_len; // length of query string used for direct queries - HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare - - pdo_param_type* bound_column_param_types; - bool fetch_numeric; - bool fetch_datetime; -}; - - -//********************************************************************************************************************************* -// Error Handling Functions -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct pdo_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// called when an error occurs in the core layer. These routines are set as the error_callback in a -// context. The context is passed to this function since it contains the function - -bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, - _In_opt_ va_list* print_args ); -bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, - _In_opt_ va_list* print_args ); -bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, - _In_opt_ va_list* print_args ); - -// common routine to transfer a sqlsrv_context's error to a PDO zval -void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval ); - -// reset the errors from the last operation -inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) -{ - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error - - // release the last statement from the dbh so that error handling won't have a statement passed to it - if( dbh->query_stmt ) { - dbh->query_stmt = NULL; - zval_ptr_dtor( &dbh->query_stmt_zval ); - } - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( dbh->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( dbh->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); - -inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt ) -{ - strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( stmt->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( stmt->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); - -// validate the driver objects -#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } -#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } - - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// List of PDO specific error messages. -enum PDO_ERROR_CODES { - - PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, - PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, - PDO_SQLSRV_ERROR_INVALID_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, - PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, - PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, - PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, - PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, - PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, - PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, - PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, - PDO_SQLSRV_ERROR_PARAM_PARSE, - PDO_SQLSRV_ERROR_LAST_INSERT_ID, - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, - PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_DSN_STRING, - PDO_SQLSRV_ERROR_INVALID_DSN_KEY, - PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, - PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, - PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, - PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, - SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, - PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, - PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, - PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, - PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED, - PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED -}; - -extern pdo_error PDO_ERRORS[]; - -#define THROW_PDO_ERROR( ctx, custom, ... ) \ - call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ - throw pdo::PDOException(); - -namespace pdo { - - // an error which occurred in our PDO driver, NOT an exception thrown by PDO - struct PDOException : public core::CoreException { - - PDOException() : CoreException() - { - } - }; - -} // namespace pdo - -// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro -void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); - #endif /* PHP_PDO_SQLSRV_H */ - diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h new file mode 100644 index 000000000..4b50bbc75 --- /dev/null +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -0,0 +1,409 @@ +#ifndef PHP_PDO_SQLSRV_INT_H +#define PHP_PDO_SQLSRV_INT_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_pdo_sqlsrv_int.h +// +// Contents: Internal declarations for the extension +// +// Microsoft Drivers 5.6 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +extern "C" { + #include "pdo/php_pdo.h" + #include "pdo/php_pdo_driver.h" +} + +#include +#include + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +// henv context for creating connections +extern sqlsrv_context* g_pdo_henv_cp; +extern sqlsrv_context* g_pdo_henv_ncp; + +// used for getting the version information +extern HMODULE g_sqlsrv_hmodule; + +// macros used to access the global variables. Use these to make global variable access agnostic to threads +#ifdef ZTS +#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) +#else +#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" +#define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PREFIX "pdo_sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, + zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, + client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) +PHP_INI_END() + + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// sqlsrv driver specific PDO attributes +enum PDO_SQLSRV_ATTR { + + // The custom attributes for this driver: + SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, + SQLSRV_ATTR_QUERY_TIMEOUT, + SQLSRV_ATTR_DIRECT_QUERY, + SQLSRV_ATTR_CURSOR_SCROLL_TYPE, + SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, + SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, + SQLSRV_ATTR_FETCHES_DATETIME_TYPE, + SQLSRV_ATTR_FORMAT_DECIMALS, + SQLSRV_ATTR_DECIMAL_PLACES +}; + +// valid set of values for TransactionIsolation connection option +namespace PDOTxnIsolationValues { + + const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; + const char READ_COMMITTED[] = "READ_COMMITTED"; + const char REPEATABLE_READ[] = "REPEATABLE_READ"; + const char SERIALIZABLE[] = "SERIALIZABLE"; + const char SNAPSHOT[] = "SNAPSHOT"; +} + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// Basic string parser +class string_parser +{ + protected: + const char* orig_str; + sqlsrv_context* ctx; + int len; + int pos; + unsigned int current_key; + HashTable* element_ht; + inline bool next(void); + inline bool is_eos(void); + inline bool is_white_space( _In_ char c ); + bool discard_white_spaces(void); + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// PDO DSN Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN connection string. +class conn_string_parser : private string_parser +{ + enum States + { + FirstKeyValuePair, + Key, + Value, + ValueContent1, + ValueContent2, + RCBEncountered, + NextKeyValuePair, + }; + + private: + const char* current_key_name; + int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); + void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC); + + protected: + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); + + public: + conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); + void parse_conn_string( TSRMLS_D ); +}; + + +//********************************************************************************************************************************* +// PDO Query Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN named placeholders. +class sql_string_parser : private string_parser +{ + private: + bool is_placeholder_char(char); + public: + void add_key_int_value_pair( _In_ unsigned int value TSRMLS_DC ); + sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht); + void parse_sql_string(TSRMLS_D); +}; + + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* + +extern const connection_option PDO_CONN_OPTS[]; + +int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC); + +// a core layer pdo dbh object. This object inherits and overrides the statement factory +struct pdo_sqlsrv_dbh : public sqlsrv_conn { + + zval* stmts; + bool direct_query; + long query_timeout; + zend_long client_buffer_max_size; + bool fetch_numeric; + bool fetch_datetime; + bool format_decimals; + short decimal_places; + + pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_encoding : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_pdo_scrollable : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_direct_query : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_cursor_scroll_type : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_emulate_prepares : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_fetch_numeric : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_fetch_datetime : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; + +// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary +struct pdo_sqlsrv_stmt : public sqlsrv_stmt { + + pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + direct_query( false ), + direct_query_subst_string( NULL ), + direct_query_subst_string_len( 0 ), + placeholders(NULL), + bound_column_param_types( NULL ), + fetch_numeric( false ), + fetch_datetime( false ) + { + pdo_sqlsrv_dbh* db = static_cast( c ); + direct_query = db->direct_query; + fetch_numeric = db->fetch_numeric; + fetch_datetime = db->fetch_datetime; + format_decimals = db->format_decimals; + decimal_places = db->decimal_places; + } + + virtual ~pdo_sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types + virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); + + bool direct_query; // flag set if the query should be executed directly or prepared + const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters + size_t direct_query_subst_string_len; // length of query string used for direct queries + HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare + + pdo_param_type* bound_column_param_types; + bool fetch_numeric; + bool fetch_datetime; +}; + + +//********************************************************************************************************************************* +// Error Handling Functions +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct pdo_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// called when an error occurs in the core layer. These routines are set as the error_callback in a +// context. The context is passed to this function since it contains the function + +bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, + _In_opt_ va_list* print_args ); +bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, + _In_opt_ va_list* print_args ); +bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, + _In_opt_ va_list* print_args ); + +// common routine to transfer a sqlsrv_context's error to a PDO zval +void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval ); + +// reset the errors from the last operation +inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) +{ + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error + + // release the last statement from the dbh so that error handling won't have a statement passed to it + if( dbh->query_stmt ) { + dbh->query_stmt = NULL; + zval_ptr_dtor( &dbh->query_stmt_zval ); + } + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( dbh->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( dbh->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); + +inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt ) +{ + strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( stmt->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( stmt->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); + +// validate the driver objects +#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } +#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } + + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// List of PDO specific error messages. +enum PDO_ERROR_CODES { + + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + PDO_SQLSRV_ERROR_INVALID_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + PDO_SQLSRV_ERROR_PARAM_PARSE, + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, + PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, + PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED, + PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED +}; + +extern pdo_error PDO_ERRORS[]; + +#define THROW_PDO_ERROR( ctx, custom, ... ) \ + call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ + throw pdo::PDOException(); + +namespace pdo { + + // an error which occurred in our PDO driver, NOT an exception thrown by PDO + struct PDOException : public core::CoreException { + + PDOException() : CoreException() + { + } + }; + +} // namespace pdo + +// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro +void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); + + +#endif /* PHP_PDO_SQLSRV_INT_H */ diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 7b74950aa..fdbeaa576 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 0e3beadd1..b664159c5 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 0c29e81f0..907b16479 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index 6f494dc4c..6aac5a051 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index bcd250b62..a2bfceaa9 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 8ec79e0c1..3d195fe9c 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -730,7 +730,7 @@ bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t if (value_len <= 0) return false; - if( ! stricmp( value, AzureADOptions::AZURE_AUTH_SQL_PASSWORD ) || ! stricmp( value, AzureADOptions::AZURE_AUTH_AD_PASSWORD ) ) { + if (!stricmp(value, AzureADOptions::AZURE_AUTH_SQL_PASSWORD) || !stricmp(value, AzureADOptions::AZURE_AUTH_AD_PASSWORD) || !stricmp(value, AzureADOptions::AZURE_AUTH_AD_MSI)) { return true; } @@ -769,16 +769,18 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou bool mars_mentioned = false; connection_option const* conn_opt; bool access_token_used = false; + bool authentication_option_used = zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION); try { - // First of all, check if access token is specified. If so, check if UID, PWD, Authentication exist + // Since connection options access token and authentication cannot coexist, check if both of them are used. + // If access token is specified, check UID and PWD as well. // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { bool invalidOptions = false; // UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string, // even if they may be empty strings. Likewise if the keyword Authentication exists - if (uid != NULL || pwd != NULL || zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION)) { + if (uid != NULL || pwd != NULL || authentication_option_used) { invalidOptions = true; } @@ -789,11 +791,44 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou access_token_used = true; } + // Check if Authentication is ActiveDirectoryMSI + // https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview + bool activeDirectoryMSI = false; + if (authentication_option_used) { + zval* auth_option = NULL; + auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION); + + char* option = Z_STRVAL_P(auth_option); + + if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { + activeDirectoryMSI = true; + + // There are two types of managed identities: + // (1) A system-assigned managed identity: UID must be NULL + // (2) A user-assigned managed identity: UID defined but must not be an empty string + // In both cases, PWD must be NULL + + bool invalid = false; + if (pwd != NULL) { + invalid = true; + } else { + if (uid != NULL && strnlen_s(uid) == 0) { + invalid = true; + } + } + + CHECK_CUSTOM_ERROR(invalid, conn, SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL ) { + throw core::CoreException(); + } + } + } + // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection -- but not when access token is used, because they are incompatible - if (!access_token_used) { + + // If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used, + // because they are incompatible + if (!access_token_used && !activeDirectoryMSI) { if (uid == NULL || strnlen_s(uid) == 0) { connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};" } diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index ea604e791..504a145b6 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -159,20 +159,3 @@ void core_sqlsrv_mshutdown( _Inout_ sqlsrv_context& henv_cp, _Inout_ sqlsrv_cont return; } - -// DllMain for the extension. -#ifdef _WIN32 -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 diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index f4f00b371..93427bd76 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 400e9ea2f..886d5e829 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -191,6 +191,7 @@ const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; namespace AzureADOptions { const char AZURE_AUTH_SQL_PASSWORD[] = "SqlPassword"; const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword"; + const char AZURE_AUTH_AD_MSI[] = "ActiveDirectoryMsi"; } // the message returned by ODBC Driver for SQL Server @@ -1777,6 +1778,7 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, SQLSRV_ERROR_INVALID_DECIMAL_PLACES, + SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 3b263fe22..9cac96a53 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -2770,10 +2770,10 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* { SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); buffer_len = Z_STRLEN_P( param_z ); + SQLLEN original_len = buffer_len; SQLLEN expected_len; SQLLEN buffer_null_extra; SQLLEN elem_size; - SQLLEN without_null_len; // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, // as is a n(var)char/ntext field being returned as a binary field. @@ -2801,9 +2801,6 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; - // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = field_size * elem_size; - // increment to include the null terminator since the Zend length doesn't include the null terminator buffer_len += elem_size; @@ -2821,8 +2818,10 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. - // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + // initialize the newly allocated space + char *p = ZSTR_VAL(param_z_string); + p = p + original_len; + memset(p, '\0', expected_len - original_len); ZVAL_NEW_STR(param_z, param_z_string); // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 826fcf2ed..d822d4a85 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 4aa2b37fd..515eb38b1 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index f3545de2e..4ddccc52b 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index 953c25804..f46e2b1df 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index ba5087b52..8a1f3732c 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 76e2eda8e..cd71452a6 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 6328f1fbd..a572ab021 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 38ee64b7e..e67bbf226 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -638,7 +638,7 @@ size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); } @@ -664,7 +664,7 @@ size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode ); } @@ -952,7 +952,7 @@ size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cc return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); } @@ -972,7 +972,7 @@ size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1 + mplat_wcslen(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert(dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode); } diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index b440a95aa..2392c5e39 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index b000b640d..053fb199c 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index d035e3666..390d03cc7 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 256fb51d9..c7c554813 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 5 +#define SQLVERSION_MINOR 6 #define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 1 +#define PREVIEW 0 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. diff --git a/source/shared/xplat.h b/source/shared/xplat.h index bb4888ea2..1b1956391 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 571c62b2a..91db26523 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 88a97baa0..34e7ffb50 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index 7bdac15e1..bc81cf58c 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index 5af9e7cd5..20d4d8589 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ 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.5 for PHP for SQL Server +dnl Microsoft Drivers 5.6 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License @@ -40,7 +40,10 @@ if test "$PHP_SQLSRV" != "no"; then shared/StringFunctions.cpp \ " AC_MSG_CHECKING([for SQLSRV headers]) - if test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then + 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/ diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index dc9f0b5fb..5a2477e99 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -27,7 +27,9 @@ if( PHP_SQLSRV != "no" ) { 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")) { - ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); + if (PHP_PDO_SQLSRV == "no" || PHP_SQLSRV_SHARED) { + 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" ); diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 1de0c6b6f..532934f72 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" #include #include diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 9079ac752..80014524d 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -16,12 +16,18 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_sqlsrv.h" +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" { @@ -685,3 +691,23 @@ PHP_MINFO_FUNCTION(sqlsrv) 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 diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index f60c6c849..4de7f5c9a 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -22,84 +22,44 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "core_sqlsrv.h" -#include "version.h" +#include "php.h" -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -#endif +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* -extern "C" { +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) -#endif +// global objects for errors and warnings. These are returned by sqlsrv_errors. +zval errors; +zval warnings; -#ifdef ZTS -#include "TSRM.h" -#endif +// 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; -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif +ZEND_END_MODULE_GLOBALS(sqlsrv) -#if defined(_MSC_VER) -#pragma warning(pop) -#endif +ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); -#if PHP_MAJOR_VERSION < 7 -#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP -#endif +// 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 ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) +#if defined(ZTS) +ZEND_TSRMLS_CACHE_EXTERN(); #endif -} // extern "C" //********************************************************************************************************************************* // 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; - // variables set during initialization extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP -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; - -extern bool isVistaOrGreater; // used to determine if OS is Vista or Greater #define phpext_sqlsrv_ptr &g_sqlsrv_module_entry @@ -114,9 +74,11 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv); // module info function (info returned by phpinfo()) PHP_MINFO_FUNCTION(sqlsrv); + //********************************************************************************************************************************* -// Connection +// Functions //********************************************************************************************************************************* + PHP_FUNCTION(sqlsrv_connect); PHP_FUNCTION(sqlsrv_begin_transaction); PHP_FUNCTION(sqlsrv_client_info); @@ -127,90 +89,6 @@ PHP_FUNCTION(sqlsrv_prepare); PHP_FUNCTION(sqlsrv_rollback); PHP_FUNCTION(sqlsrv_server_info); -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 TSRMLS_DC ) : - sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), - 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 TSRMLS_DC ); - -//********************************************************************************************************************************* -// 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 TSRMLS_DC ); -}; - -// 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 TSRMLS_DC ); - - virtual ~ss_sqlsrv_stmt( void ); - - void new_result_set( TSRMLS_D ); - - // 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 ) - { - } -}; - -// *** statement functions *** PHP_FUNCTION(sqlsrv_cancel); PHP_FUNCTION(sqlsrv_execute); PHP_FUNCTION(sqlsrv_fetch); @@ -226,20 +104,6 @@ PHP_FUNCTION(sqlsrv_num_rows); PHP_FUNCTION(sqlsrv_rows_affected); PHP_FUNCTION(sqlsrv_send_stream_data); -// resource destructor -void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); - -// "internal" statement functions shared by functions in conn.cpp and stmt.cpp -void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); -bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function - TSRMLS_DC ); -void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC ); - -//********************************************************************************************************************************* -// Type Functions -//********************************************************************************************************************************* - // 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. @@ -258,368 +122,14 @@ PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR); PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM); PHP_FUNCTION(SQLSRV_PHPTYPE_STRING); -//********************************************************************************************************************************* -// Global variables -//********************************************************************************************************************************* - -extern "C" { - -// 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; - -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 - -// 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." - -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 ) -PHP_INI_END() - -//********************************************************************************************************************************* -// Configuration -//********************************************************************************************************************************* // 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); -//********************************************************************************************************************************* -// 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_INVALID_AUTHENTICATION_OPTION, - 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_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args ); - // *** extension error functions *** PHP_FUNCTION(sqlsrv_errors); -// 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 ); -// 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 ); - -// *** internal error macros and functions *** -bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); - -// release current error lists and set to NULL -inline void reset_errors( TSRMLS_D ) -{ - 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 TSRMLS_CC, false /*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( 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; \ - LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); - -#define SET_FUNCTION_NAME( context ) \ -{ \ - (context).set_func( _FN_ ); \ -} - -// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro -void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); - -// 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 TSRMLS_DC) - { - int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( 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 -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; - - // reset the errors from the previous API call - reset_errors( TSRMLS_C ); - - 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() TSRMLS_CC, const_cast( param_spec ), &rsrc ); - break; - - case 1: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); - break; - - case 2: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1] ); - break; - - case 3: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2] ); - break; - - case 4: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3] ); - break; - - case 5: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4] ); - break; - - case 6: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( 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( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, 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_H */ diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h new file mode 100644 index 000000000..c294f465b --- /dev/null +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -0,0 +1,468 @@ +#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.6 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." + +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 ) +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 TSRMLS_DC ) : + sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), + 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 TSRMLS_DC ); + + +//********************************************************************************************************************************* +// 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 TSRMLS_DC ); +}; + +// 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 TSRMLS_DC ); + + virtual ~ss_sqlsrv_stmt( void ); + + void new_result_set( TSRMLS_D ); + + // 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 TSRMLS_DC ); + +// "internal" statement functions shared by functions in conn.cpp and stmt.cpp +void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); +bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function + TSRMLS_DC ); +void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC ); + + +//********************************************************************************************************************************* +// 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_INVALID_AUTHENTICATION_OPTION, + 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_ bool warning TSRMLS_DC, _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 ); +// 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 ); + +// *** internal error macros and functions *** +bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); + +// release current error lists and set to NULL +inline void reset_errors( TSRMLS_D ) +{ + 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 TSRMLS_CC, false /*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( 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; \ + LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); + +#define SET_FUNCTION_NAME( context ) \ +{ \ + (context).set_func( _FN_ ); \ +} + +// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro +void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); + +// 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 TSRMLS_DC) + { + int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( 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 +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; + + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + 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() TSRMLS_CC, const_cast( param_spec ), &rsrc ); + break; + + case 1: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); + break; + + case 2: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1] ); + break; + + case 3: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2] ); + break; + + case 4: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3] ); + break; + + case 5: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4] ); + break; + + case 6: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( 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( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, 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 */ diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 1f0357acc..c366e0305 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -18,7 +18,12 @@ //--------------------------------------------------------------------------------------------------------------------------------- // *** header files *** -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" + #ifdef _WIN32 #include #endif // _WIN32 diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 88eefb55c..bbe471322 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index d4be03f5b..cec66c3f3 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -19,7 +19,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" namespace { @@ -368,7 +372,7 @@ ss_error SS_ERRORS[] = { }, { SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - { IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -62, false } + { IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.", -62, false } }, { SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED, @@ -432,6 +436,10 @@ ss_error SS_ERRORS[] = { 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_AAD_MSI_UID_PWD_NOT_NULL, + { IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -118, false} + }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index cf2ff605c..d327d8599 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -624,6 +624,33 @@ function IsDaasMode() return ($daasMode ? true : false); } +function isAzureDW() +{ + // Check if running Azure Data Warehouse + // For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql + try { + $conn = connect(); + + // Check if running Azure Data Warehouse + // For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql + $tsql = "SELECT SERVERPROPERTY ('edition'), SERVERPROPERTY ('EngineEdition')"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + $engEd = intval($result[1]); + + if ($edition == "SQL Azure" && $engEd == 6) { + return true; + } else { + return false; + } + } catch (Exception $e) { + echo $e->getMessage(); + die("skip Failed to connect or could not fetch edition info."); + } +} + function FatalError($errorMsg) { if (!IsPdoMode()) { diff --git a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt index 9fc7cd955..5513290b6 100644 --- a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt +++ b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt @@ -6,7 +6,7 @@ emalloc (which only allocate memory in the memory space allocated for the PHP pr --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + --FILE-- --FILE-- @@ -21,28 +23,38 @@ try { insertRow($conn, $tableName2, array("c1_int" => 990021574, "c2_varchar" => ">vh~Ö.bÃ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÃÃœh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÃö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÃBî@~AZöÃOßC@äoÃuCÃœ,ÃÄa:îäÄÖý:h*ouªuÃ¥vUz_ArßAªãaãvÃÃ¥AUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Ãœa~/v@Ã¥Az©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zÃ¥,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÃÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÃ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢CßovÃ¥+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÃböÄåbîðîÃa~©ßîÄßã<î>Ã¥Bã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ãœ,ßAÃ>,ðßß+ßÜ©|Ãr©bCðâüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ãa@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ãœ,@U*ÃvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÃÃœOÃoðßðÃUýZÃB:+ÄÃã£")); $size = 2; - $stmt = $conn->prepare("SELECT * FROM $tableName1", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); - $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + $stmt = $conn->prepare("SELECT * FROM $tableName1", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); + $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); echo("Client Buffer Size in KB: $attr\n"); - $stmt->execute(); + $stmt->execute(); $numRows = 0; while ($result = $stmt->fetch()) { $numRows++; } echo ("Number of rows: $numRows\n"); - $size = 3; - $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); - $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + $size = 3; + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); + $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); echo("Client Buffer Size in KB: $attr\n"); - $stmt->execute(); + $stmt->execute(); $numRows = 0; while ($result = $stmt->fetch()) { $numRows++; } - echo ("Number of rows: $numRows\n"); + $size = 1; + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); + $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + try { + $stmt->execute(); + echo "Expect this to fail!!\n"; + } catch (PDOException $e) { + var_dump($e->getMessage()); + } + dropTable($conn, $tableName1); dropTable($conn, $tableName2); unset($stmt); @@ -58,4 +70,6 @@ Client Buffer Size in KB: 2 Number of rows: 1 Client Buffer Size in KB: 3 Number of rows: 1 +Client Buffer Size in KB: 1 +string(65) "SQLSTATE[IMSSP]: Memory limit of 1 KB exceeded for buffered query" Done diff --git a/test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt b/test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt new file mode 100644 index 000000000..b501bd7b9 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt @@ -0,0 +1,94 @@ +--TEST-- +GitHub issue #228 - how max client buffer size affects the fetching of data +--DESCRIPTION-- +A pdo_sqlsrv variation of the example in GitHub issue 228, using PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE the connection attribute. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + } catch (PDOException $e) { + if (strpos($e->getMessage(), $error) === false) { + echo $e->getMessage() . "\n"; + } + } +} + +try { + // Connect + $conn = connect(); + + $error = 'The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not positive. Only positive numbers are valid for this attribute.'; + testErrors($conn, 0, $error); + testErrors($conn, 2.99, $error); + + // Create 2 tables + $tableName1 = 'pdo_228_1'; + $tableName2 = 'pdo_228_2'; + + createTable($conn,$tableName1, array("c1_int" => "int", "c2_varchar" => "varchar(1000)")); + insertRow($conn, $tableName1, array("c1_int" => 990021574, "c2_varchar" => ">vh~Ö.bÃ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÃÃœh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÃö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÃBî@~AZöÃOßC@äoÃuCÃœ,ÃÄa:îäÄÖý:h*ouªuÃ¥vUz_ArßAªãaãvÃÃ¥AUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Ãœa~/v@Ã¥Az©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zÃ¥,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÃÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÃ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢CßovÃ¥+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÃböÄåbîðîÃa~©ßîÄßã<î>Ã¥Bã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ãœ,ßAÃ>,ðßß+ßÜ©|Ãr©bCðâüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ãa@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ãœ,@U*ÃvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÃÃœOÃoðßðÃUýZÃB:+ÄÃã£")); + + createTable($conn,$tableName2, array("c1_int" => "int", "c2_varchar" => "varchar(max)")); + insertRow($conn, $tableName2, array("c1_int" => 990021574, "c2_varchar" => ">vh~Ö.bÃ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÃÃœh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÃö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÃBî@~AZöÃOßC@äoÃuCÃœ,ÃÄa:îäÄÖý:h*ouªuÃ¥vUz_ArßAªãaãvÃÃ¥AUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Ãœa~/v@Ã¥Az©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zÃ¥,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÃÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÃ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢CßovÃ¥+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÃböÄåbîðîÃa~©ßîÄßã<î>Ã¥Bã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ãœ,ßAÃ>,ðßß+ßÜ©|Ãr©bCðâüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ãa@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ãœ,@U*ÃvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÃÃœOÃoðßðÃUýZÃB:+ÄÃã£")); + + $size = 2; + $conn->setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + $stmt = $conn->prepare("SELECT * FROM $tableName1", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $attr = $conn->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + $stmt->execute(); + $numRows = 0; + while ($result = $stmt->fetch()) { + $numRows++; + } + echo ("Number of rows: $numRows\n"); + + $size = 3; + $conn->setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $attr = $conn->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + $stmt->execute(); + $numRows = 0; + while ($result = $stmt->fetch()) { + $numRows++; + } + + echo ("Number of rows: $numRows\n"); + + $size = 1; + $conn->setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $attr = $conn->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + try { + $stmt->execute(); + echo "Expect this to fail!!\n"; + } catch (PDOException $e) { + var_dump($e->getMessage()); + } + + dropTable($conn, $tableName1); + dropTable($conn, $tableName2); + unset($stmt); + unset($conn); + print "Done"; +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> + +--EXPECT-- +Client Buffer Size in KB: 2 +Number of rows: 1 +Client Buffer Size in KB: 3 +Number of rows: 1 +Client Buffer Size in KB: 1 +string(65) "SQLSTATE[IMSSP]: Memory limit of 1 KB exceeded for buffered query" +Done diff --git a/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt b/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt index d4c0e8112..5ae6c67b8 100644 --- a/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt +++ b/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt @@ -6,6 +6,7 @@ Verifies the functionality of PDOStatement nextRowset PHPT_EXEC=true --SKIPIF-- + --FILE-- +--FILE-- +prepare("$storedProcName @OUTPUT = :output"); + if ($inout) { + $paramType = PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = PDO::PARAM_STR; + } + $stmt->bindParam('output', $output, $paramType, $size); + + $stmt->execute(); + + // The output param should be doubled in size for wide characters. + // However, it should not contain any data so after trimming it + // should be merely an empty string because it was originally set to null + $len = strlen($output); + $result = trim($output); + + if ($len != ($size * 2) || $result !== "" ) { + echo "Unexpected output param for $dataType: "; + var_dump($output); + } + + $stmt->closeCursor(); + if (!is_null($output)) { + echo "Output param should be null when finalized!"; + } + unset($stmt); + } catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; + } +} + +try { + // This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION + // $conn = connect(); + $conn = new PDO( "sqlsrv:server=$server; Database = $databaseName", $uid, $pwd); + $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + $dataTypes = array("VARCHAR(256)", "VARCHAR(512)", "VARCHAR(max)", "NVARCHAR(256)", "NVARCHAR(512)", "NVARCHAR(max)"); + for ($i = 0, $p = 3; $i < count($dataTypes); $i++, $p++) { + // Create the stored procedure first + $storedProcName = "spNullOutputParam" . $i; + $procArgs = "@OUTPUT $dataTypes[$i] OUTPUT"; + $procCode = "SELECT 1, 2, 3"; + + createProc($conn, $storedProcName, $procArgs, $procCode); + getOutputParam($conn, $storedProcName, $dataTypes[$i], false); + getOutputParam($conn, $storedProcName, $dataTypes[$i], true); + + // Drop the stored procedure + dropProc($conn, $storedProcName); + } + + echo "Done\n"; + + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt index 0aea766f4..105f63953 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt @@ -12,30 +12,27 @@ require_once("MsSetup.inc"); // $connectionInfo = "Database = $databaseName; Authentication = SqlPassword; TrustServerCertificate = true;"; -try -{ - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); +try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); echo "Connected successfully with Authentication=SqlPassword.\n"; -} -catch( PDOException $e ) -{ +} catch (PDOException $e) { echo "Could not connect with Authentication=SqlPassword.\n"; - print_r( $e->getMessage() ); + print_r($e->getMessage()); echo "\n"; } -$stmt = $conn->query( "SELECT count(*) FROM cd_info" ); -if ( $stmt === false ) -{ +// For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql +$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->query("SELECT SERVERPROPERTY('EngineEdition')"); +if ($stmt === false) { echo "Query failed.\n"; -} -else -{ - $result = $stmt->fetch(); - var_dump( $result ); +} else { + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + var_dump($edition); } -$conn = null; +unset($conn); /////////////////////////////////////////////////////////////////////////////////////////// // Test Azure AD with integrated authentication. This should fail because @@ -43,16 +40,13 @@ $conn = null; // $connectionInfo = "Authentication = ActiveDirectoryIntegrated; TrustServerCertificate = true;"; -try -{ - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo" ); +try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo"); echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; - $conn = null; -} -catch( PDOException $e ) -{ + unset($conn); +} catch (PDOException $e) { echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; - print_r( $e->getMessage() ); + print_r($e->getMessage()); echo "\n"; } @@ -65,35 +59,24 @@ $azureDatabase = $adDatabase; $azureUsername = $adUser; $azurePassword = $adPassword; -if ($azureServer != 'TARGET_AD_SERVER') -{ +if ($azureServer != 'TARGET_AD_SERVER') { $connectionInfo = "Authentication = ActiveDirectoryPassword; TrustServerCertificate = false"; - try - { - $conn = new PDO( "sqlsrv:server = $azureServer ; $connectionInfo", $azureUsername, $azurePassword ); + try { + $conn = new PDO("sqlsrv:server = $azureServer ; $connectionInfo", $azureUsername, $azurePassword); echo "Connected successfully with Authentication=ActiveDirectoryPassword.\n"; - } - catch( PDOException $e ) - { + } catch (PDOException $e) { echo "Could not connect with ActiveDirectoryPassword.\n"; - print_r( $e->getMessage() ); + print_r($e->getMessage()); echo "\n"; } -} -else -{ +} else { echo "Not testing with Authentication=ActiveDirectoryPassword.\n"; } ?> --EXPECTF-- Connected successfully with Authentication=SqlPassword. -array(2) { - [""]=> - string(1) "7" - [0]=> - string(1) "7" -} +string(1) "%d" Could not connect with Authentication=ActiveDirectoryIntegrated. -SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. +SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported. %s with Authentication=ActiveDirectoryPassword. diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt new file mode 100644 index 000000000..00a0c9507 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt @@ -0,0 +1,114 @@ +--TEST-- +Test some error conditions of Azure AD Managed Identity support +--DESCRIPTION-- +This test expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- +getMessage(), $expectedError) === false) { + echo "AzureAD Managed Identity test: expected to fail with $msg\n"; + + print_r($exception->getMessage()); + echo "\n"; + } +} + +function connectWithInvalidOptions() +{ + global $server; + + $message = 'AzureAD Managed Identity test: expected to fail with '; + $expectedError = 'When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted'; + + $uid = ''; + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'empty UID provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = ''; + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'empty PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = 'dummy'; + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + $connectionInfo = "Authentication = ActiveDirectoryMsi; AccessToken = '123';"; + $testCase = 'AccessToken option'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo"); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); +} + +function connectInvalidServer() +{ + global $server, $driver, $uid, $pwd; + + try { + $conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); + + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $version = explode(".", $msodbcsqlVer); + + if ($version[0] < 17 || $version[1] < 3) { + //skip the rest of this test, which requires ODBC driver 17.3 or above + return; + } + unset($conn); + + // Try connecting to an invalid server, should get an exception from ODBC + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'invalidServer'; + try { + $conn = new PDO("sqlsrv:server = invalidServer; $connectionInfo", null, null); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + // TODO: check the exception message here + } + } catch(PDOException $e) { + print_r($e->getMessage()); + } +} + +require_once('MsSetup.inc'); + +// Test some error conditions +connectWithInvalidOptions(); + +// Make a connection to an invalid server +connectInvalidServer(); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_passwords.phpt b/test/functional/pdo_sqlsrv/pdo_passwords.phpt deleted file mode 100644 index c672fc95e..000000000 --- a/test/functional/pdo_sqlsrv/pdo_passwords.phpt +++ /dev/null @@ -1,57 +0,0 @@ ---TEST-- -Test password with non alphanumeric characters ---DESCRIPTION-- -The first three cases should have no problem connecting. Only the last case fails because the -right curly brace should be escaped with another right brace. -In Azure for this test to pass do not specify any particular database when connecting ---SKIPIF-- - ---FILE-- -getMessage() . "\n"); -} -try { - // Test 2 - $conn = new PDO($dsn, "test_password2", "!}} ;4triou"); - if (!$conn) { - echo "Test 2: Should have connected."; - } - unset($conn); -} catch (PDOException $e) { - print_r($e->getMessage() . "\n"); -} -try { - // Test 3 - $conn = new PDO($dsn, "test_password3", "! ;4triou}}"); - if (!$conn) { - echo "Test 3: Should have connected."; - } - unset($conn); -} catch (PDOException $e) { - print_r($e->getMessage() . "\n"); -} -// Test invalid password. -try { - // Test 4 - $conn = new PDO($dsn, "test_password3", "! ;4triou}"); -} catch (PDOException $e) { - print_r($e->getMessage()); - exit; -} - -?> - ---EXPECTREGEX-- -SQLSTATE\[IMSSP\]: An unescaped right brace \(}\) was found in either the user name or password\. All right braces must be escaped with another right brace \(}}\)\. diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt index d0da03e1d..01774f081 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt @@ -4,6 +4,7 @@ Test PDO::prepare() with PDO::ATTR_EMULATE_PREPARES. PHPT_EXEC=true --SKIPIF-- + --FILE-- \ No newline at end of file diff --git a/test/functional/setup/168256.sql b/test/functional/setup/168256.sql index 82dbe487e..f4484c34f 100644 --- a/test/functional/setup/168256.sql +++ b/test/functional/setup/168256.sql @@ -1,6 +1,3 @@ -USE $(dbname) -GO - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[168256]') AND type in (N'U')) diff --git a/test/functional/setup/cd_info.sql b/test/functional/setup/cd_info.sql index 40d3bb048..96240acf9 100644 --- a/test/functional/setup/cd_info.sql +++ b/test/functional/setup/cd_info.sql @@ -1,11 +1,8 @@ -USE $(dbname) -GO - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tracks]') AND type in (N'U')) BEGIN -ALTER TABLE $(dbname)..[tracks] DROP CONSTRAINT [FK__tracks__asin__7F60ED59] +ALTER TABLE [tracks] DROP CONSTRAINT [FK__tracks__asin__7F60ED59] END GO diff --git a/test/functional/setup/cleanup_dbs.py b/test/functional/setup/cleanup_dbs.py index 86406303b..db78ea032 100644 --- a/test/functional/setup/cleanup_dbs.py +++ b/test/functional/setup/cleanup_dbs.py @@ -3,10 +3,8 @@ import os import sys -import subprocess import platform import argparse -from subprocess import Popen, PIPE from exec_sql_scripts import * if __name__ == '__main__': @@ -25,8 +23,9 @@ sys.exit(1) conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' - - executeSQLscript( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'drop_db.sql'), conn_options, args.DBNAME) + + sql_script = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'drop_db.sql'); + manageTestDB(sql_script, conn_options, args.DBNAME) # if Windows, remove self signed certificate using ps command if platform.system() == 'Windows': diff --git a/test/functional/setup/create_db.sql b/test/functional/setup/create_db.sql index d8dee7acf..2c13bc32d 100644 --- a/test/functional/setup/create_db.sql +++ b/test/functional/setup/create_db.sql @@ -1,13 +1,5 @@ -USE [master] -GO - -IF EXISTS (SELECT name FROM sys.databases WHERE name = '$(dbname)' ) - -BEGIN -DROP DATABASE $(dbname) -END - -CREATE DATABASE $(dbname) - -GO - +IF EXISTS (SELECT name FROM sys.databases WHERE name = 'TEST_DB' ) DROP DATABASE TEST_DB + +CREATE DATABASE TEST_DB +GO + diff --git a/test/functional/setup/create_logins_azure.sql b/test/functional/setup/create_logins_azure.sql deleted file mode 100644 index ecc5530a3..000000000 --- a/test/functional/setup/create_logins_azure.sql +++ /dev/null @@ -1,18 +0,0 @@ ---for this script to work in Azure, use sqlcmd to connect to master database -IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password') -BEGIN - CREATE LOGIN test_password WITH PASSWORD='! ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password2') -BEGIN - CREATE LOGIN test_password2 WITH PASSWORD='!} ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password3') -BEGIN - CREATE LOGIN test_password3 WITH PASSWORD='! ;4triou}'; -END -GO diff --git a/test/functional/setup/create_users_azure.sql b/test/functional/setup/create_users_azure.sql deleted file mode 100644 index af5770639..000000000 --- a/test/functional/setup/create_users_azure.sql +++ /dev/null @@ -1,21 +0,0 @@ ---for this script to work in Azure, create_logins_azure.sql must have been invoked beforehand ---assuming these logins exist, use sqlcmd to connect to a test database ---these users will be granted access to that database -IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password') -BEGIN - CREATE USER test_password FROM LOGIN test_password; -END -GO - -IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password2') -BEGIN - CREATE USER test_password2 FROM LOGIN test_password2; -END -GO - -IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password3') -BEGIN - CREATE USER test_password3 FROM LOGIN test_password3; -END -GO - diff --git a/test/functional/setup/drop_db.sql b/test/functional/setup/drop_db.sql index d45743f55..b31a055f2 100644 --- a/test/functional/setup/drop_db.sql +++ b/test/functional/setup/drop_db.sql @@ -1,8 +1 @@ -USE [master] -GO - -IF EXISTS (SELECT name FROM sys.databases WHERE name = '$(dbname)' ) - -BEGIN -DROP DATABASE $(dbname) -END +IF EXISTS (SELECT name FROM sys.databases WHERE name = 'TEST_DB' ) DROP DATABASE TEST_DB diff --git a/test/functional/setup/exec_sql_scripts.py b/test/functional/setup/exec_sql_scripts.py index 808955889..0ab264343 100644 --- a/test/functional/setup/exec_sql_scripts.py +++ b/test/functional/setup/exec_sql_scripts.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 -# contains helper methods +# contains helper methods import os -import sys import subprocess -import platform -import argparse from subprocess import Popen, PIPE def executeCommmand(inst_command): @@ -15,29 +12,17 @@ def executeCommmand(inst_command): print (oo) def executeSQLscript(sqlfile, conn_options, dbname): - if platform.system() == 'Windows': - executeSQLscriptWindows(sqlfile, conn_options, dbname) - elif platform.system() == 'Linux' or platform.system() == 'Darwin': - executeSQLscriptUnix(sqlfile, conn_options, dbname) - -def executeSQLscriptWindows(sqlfile, conn_options, dbname): - inst_command = 'sqlcmd ' + conn_options + ' -i ' + sqlfile + ' -v dbname =' + dbname + inst_command = 'sqlcmd -I ' + conn_options + ' -i ' + sqlfile + ' -d ' + dbname executeCommmand(inst_command) -def executeSQLscriptUnix(sqlfile, conn_options, dbname): - # This is a workaround because sqlcmd in Unix does not support -v option for variables. - # It inserts setvar dbname into the beginning of a temp .sql file - tmpFileName = sqlfile[0:-4] + '_tmp.sql' - redirect_string = '(echo :setvar dbname {0}) > {2}; cat {1} >> {2}; ' - sqlcmd = 'sqlcmd ' + conn_options + ' -i ' + tmpFileName +def manageTestDB(sqlfile, conn_options, dbname): + tmp_sql_file = 'test_db_tmp.sql' + if os.path.exists(tmp_sql_file): + os.remove(tmp_sql_file) + with open(sqlfile, 'r') as infile: + script = infile.read().replace('TEST_DB', dbname) + with open(tmp_sql_file, 'w') as outfile: + outfile.write(script) - # Execute a simple query via sqlcmd: without this step, the next step fails in travis CI - simple_cmd = 'sqlcmd ' + conn_options + ' -Q \"select @@Version\" ' - executeCommmand(simple_cmd) - - # inst_command = redirect_string.format(dbname, sqlfile, tmpFileName) + sqlcmd - inst_command = redirect_string.format(dbname, sqlfile, tmpFileName) - executeCommmand(inst_command) - executeCommmand(sqlcmd) - - os.remove(tmpFileName) + executeSQLscript(tmp_sql_file, conn_options, 'master') + os.remove(tmp_sql_file) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 32b821ef1..58900526d 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -1,34 +1,15 @@ #!/usr/bin/env python3 # py setup_dbs.py -dbname -azure -# OR -# py setup_dbs.py -dbname +# OR +# py setup_dbs.py -dbname import os import sys -import subprocess import platform import argparse -from subprocess import Popen, PIPE from exec_sql_scripts import * -def createLoginUsers(conn_options, dbname, azure): - if (azure.lower() == 'yes'): - # can only create logins in the master database - createLoginUsersAzure('create_logins_azure.sql', conn_options, 'master') - # create users to use those logins to access the test database (dbname) - createLoginUsersAzure('create_users_azure.sql', conn_options, dbname) - else: - executeSQLscript('test_password.sql', conn_options, dbname) - -def createLoginUsersAzure(sqlfile, conn_options, dbname): - inst_command = 'sqlcmd ' + conn_options + ' -i ' + sqlfile + ' -d ' + dbname - executeCommmand(inst_command) - def setupTestDatabase(conn_options, dbname, azure): sqlFiles = ['test_types.sql', '168256.sql', 'cd_info.sql', 'tracks.sql'] - - # for Azure, must specify the database for the sql scripts to work - if (azure.lower() == 'yes'): - conn_options += ' -d ' + dbname for sqlFile in sqlFiles: executeSQLscript(sqlFile, conn_options, dbname) @@ -41,29 +22,29 @@ def populateTables(conn_options, dbname): executeBulkCopy(conn_options, dbname, '168256', '168256') def executeBulkCopy(conn_options, dbname, tblname, datafile): - redirect_string = 'bcp {0}..[{1}] in {2}.dat -f {2}.fmt ' - inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options + redirect_string = 'bcp {0}..{1} in {2}.dat -f {2}.fmt -q' + inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options executeCommmand(inst_command) - + def setupAE(conn_options, dbname): if (platform.system() == 'Windows'): # import self signed certificate inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot" executeCommmand(inst_command) # create Column Master Key and Column Encryption Key - script_command = 'sqlcmd ' + conn_options + ' -i ae_keys.sql -d ' + dbname + script_command = 'sqlcmd -I ' + conn_options + ' -i ae_keys.sql -d ' + dbname executeCommmand(script_command) - + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-dbname', '--DBNAME', required=True) parser.add_argument('-azure', '--AZURE', required=False, default='no') args = parser.parse_args() - - try: - server = os.environ['TEST_PHP_SQL_SERVER'] - uid = os.environ['TEST_PHP_SQL_UID'] - pwd = os.environ['TEST_PHP_SQL_PWD'] + + try: + server = os.environ['TEST_PHP_SQL_SERVER'] + uid = os.environ['TEST_PHP_SQL_UID'] + pwd = os.environ['TEST_PHP_SQL_PWD'] except : print("TEST_PHP_SQL_SERVER environment variable must be set to the name of the server to use") print("TEST_PHP_SQL_UID environment variable must be set to the name of the user to authenticate with") @@ -72,20 +53,17 @@ def setupAE(conn_options, dbname): current_working_dir=os.getcwd() os.chdir(os.path.dirname(os.path.realpath(__file__))) - conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' - + conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' + # In Azure, assume an empty test database has been created using Azure portal if (args.AZURE.lower() == 'no'): - executeSQLscript('create_db.sql', conn_options, args.DBNAME) + manageTestDB('create_db.sql', conn_options, args.DBNAME) - # create login users - createLoginUsers(conn_options, args.DBNAME, args.AZURE) # create tables in the new database - setupTestDatabase(conn_options, args.DBNAME, args.AZURE) + setupTestDatabase(conn_options, args.DBNAME, args.AZURE) # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) setupAE(conn_options, args.DBNAME) - - os.chdir(current_working_dir) - + + os.chdir(current_working_dir) \ No newline at end of file diff --git a/test/functional/setup/test_password.sql b/test/functional/setup/test_password.sql deleted file mode 100644 index 683556c86..000000000 --- a/test/functional/setup/test_password.sql +++ /dev/null @@ -1,31 +0,0 @@ ---first, create new logins (user id / password pair) if not yet created -USE master; -GO - -IF NOT EXISTS (SELECT name FROM master..syslogins WHERE name = 'test_password') -BEGIN - CREATE LOGIN test_password WITH PASSWORD='! ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM master..syslogins WHERE name = 'test_password2') -BEGIN - CREATE LOGIN test_password2 WITH PASSWORD='!} ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM master..syslogins WHERE name = 'test_password3') -BEGIN - CREATE LOGIN test_password3 WITH PASSWORD='! ;4triou}'; -END -GO - ---the following users will be granted access to the test database -USE $(dbname); -GO - -CREATE USER test_password FROM LOGIN test_password; -CREATE USER test_password2 FROM LOGIN test_password2; -CREATE USER test_password3 FROM LOGIN test_password3; -GO - diff --git a/test/functional/setup/test_types.sql b/test/functional/setup/test_types.sql index eef114de5..6c5afb9b5 100644 --- a/test/functional/setup/test_types.sql +++ b/test/functional/setup/test_types.sql @@ -1,28 +1,25 @@ -USE $(dbname) -GO - CREATE TABLE [test_types] ([bigint_type] BIGINT null, - [int_type] INT null, - [smallint_type] SMALLINT null, - [tinyint_type] TINYINT null, - [bit_type] BIT null, - [decimal_type] DECIMAL(38,0) null, - [money_type] MONEY null, - [smallmoney_type] SMALLMONEY null, - [float_type] FLOAT(53) null, - [real_type] REAL null, - [datetime_type] DATETIME null, - [smalldatetime_type] SMALLDATETIME null ); + [int_type] INT null, + [smallint_type] SMALLINT null, + [tinyint_type] TINYINT null, + [bit_type] BIT null, + [decimal_type] DECIMAL(38,0) null, + [money_type] MONEY null, + [smallmoney_type] SMALLMONEY null, + [float_type] FLOAT(53) null, + [real_type] REAL null, + [datetime_type] DATETIME null, + [smalldatetime_type] SMALLDATETIME null ); GO -- maximum test -INSERT INTO $(dbname)..[test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) +INSERT INTO [test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) VALUES (9223372036854775807, 2147483647, 32767, 255, 1, 9999999999999999999999999999999999999, '12/12/1968 16:20', 922337203685477.5807, 214748.3647, 1.79E+308, 1.18E-38 ) -- minimum test -INSERT INTO $(dbname)..[test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) +INSERT INTO [test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) VALUES (-9223372036854775808, -2147483648, -32768, 0, 0, -10000000000000000000000000000000000001,'12/12/1968 16:20', -922337203685477.5808, -214748.3648, -1.79E+308, -1.18E-38 ) -- zero test -INSERT INTO $(dbname)..[test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) +INSERT INTO [test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) VALUES (0, 0, 0, 0, 0, 0, '12/12/1968 16:20', 0, 0, 0, 0) GO diff --git a/test/functional/setup/tracks.sql b/test/functional/setup/tracks.sql index 2ba3b793a..57bd914f9 100644 --- a/test/functional/setup/tracks.sql +++ b/test/functional/setup/tracks.sql @@ -1,6 +1,3 @@ -USE $(dbname) -GO - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tracks]') AND type in (N'U')) diff --git a/test/functional/sqlsrv/0013.phpt b/test/functional/sqlsrv/0013.phpt index cb2236dfd..6ccb46426 100644 --- a/test/functional/sqlsrv/0013.phpt +++ b/test/functional/sqlsrv/0013.phpt @@ -1,7 +1,7 @@ --TEST-- A test for a simple query --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + + --FILE-- + --FILE-- + --FILE-- + --FILE-- + --FILE-- \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_574_next_result.phpt b/test/functional/sqlsrv/sqlsrv_574_next_result.phpt index c21f71787..82cb4b4e9 100644 --- a/test/functional/sqlsrv/sqlsrv_574_next_result.phpt +++ b/test/functional/sqlsrv/sqlsrv_574_next_result.phpt @@ -6,6 +6,7 @@ Verifies the functionality of sqlsrv_next_result PHPT_EXEC=true --SKIPIF-- + --FILE-- +--FILE-- + "UTF-8")); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +$dataTypes = array("VARCHAR(256)", "VARCHAR(512)", "VARCHAR(max)", "NVARCHAR(256)", "NVARCHAR(512)", "NVARCHAR(max)"); +for ($i = 0, $p = 3; $i < count($dataTypes); $i++, $p++) { + // Create the stored procedure first + $storedProcName = "spNullOutputParam" . $i; + $procArgs = "@OUTPUT $dataTypes[$i] OUTPUT"; + $procCode = "SELECT 1, 2, 3"; + + createProc($conn, $storedProcName, $procArgs, $procCode); + getOutputParam($conn, $storedProcName, false, ($i < 3)); + getOutputParam($conn, $storedProcName, true, ($i < 3)); + + // Drop the stored procedure + dropProc($conn, $storedProcName); +} + +echo "Done\n"; + +sqlsrv_close($conn); + +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt index eb1da333b..51f61b276 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt @@ -13,31 +13,26 @@ require_once("MsSetup.inc"); $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, "Authentication"=>'SqlPassword', "TrustServerCertificate"=>true); -$conn = sqlsrv_connect( $server, $connectionInfo ); +$conn = sqlsrv_connect($server, $connectionInfo); -if( $conn === false ) -{ +if ($conn === false) { echo "Could not connect with Authentication=SqlPassword.\n"; - var_dump( sqlsrv_errors() ); -} -else -{ + var_dump(sqlsrv_errors()); +} else { echo "Connected successfully with Authentication=SqlPassword.\n"; } -$stmt = sqlsrv_query( $conn, "SELECT count(*) FROM cd_info" ); -if ( $stmt === false ) -{ +// For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql +$stmt = sqlsrv_query($conn, "SELECT SERVERPROPERTY('EngineEdition')"); +if (sqlsrv_fetch($stmt)) { + $edition = sqlsrv_get_field($stmt, 0); + var_dump($edition); +} else { echo "Query failed.\n"; } -else -{ - $result = sqlsrv_fetch_array( $stmt ); - var_dump( $result ); -} -sqlsrv_free_stmt( $stmt ); -sqlsrv_close( $conn ); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); /////////////////////////////////////////////////////////////////////////////////////////// // Test Azure AD with integrated authentication. This should fail because @@ -45,17 +40,14 @@ sqlsrv_close( $conn ); // $connectionInfo = array( "Authentication"=>"ActiveDirectoryIntegrated", "TrustServerCertificate"=>true ); -$conn = sqlsrv_connect( $server, $connectionInfo ); -if( $conn === false ) -{ +$conn = sqlsrv_connect($server, $connectionInfo); +if ($conn === false) { echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; $errors = sqlsrv_errors(); print_r($errors[0]); -} -else -{ +} else { echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; - sqlsrv_close( $conn ); + sqlsrv_close($conn); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -67,36 +59,25 @@ $azureDatabase = $adDatabase; $azureUsername = $adUser; $azurePassword = $adPassword; -if ($azureServer != 'TARGET_AD_SERVER') -{ - $connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword, +if ($azureServer != 'TARGET_AD_SERVER') { + $connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword, "Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>false ); - $conn = sqlsrv_connect( $azureServer, $connectionInfo ); - if( $conn === false ) - { + $conn = sqlsrv_connect($azureServer, $connectionInfo); + if ($conn === false) { echo "Could not connect with ActiveDirectoryPassword.\n"; - print_r( sqlsrv_errors() ); - } - else - { + print_r(sqlsrv_errors()); + } else { echo "Connected successfully with Authentication=ActiveDirectoryPassword.\n"; - sqlsrv_close( $conn ); + sqlsrv_close($conn); } -} -else -{ +} else { echo "Not testing with Authentication=ActiveDirectoryPassword.\n"; } ?> --EXPECTF-- Connected successfully with Authentication=SqlPassword. -array(2) { - [0]=> - int(7) - [""]=> - int(7) -} +string(1) "%d" Could not connect with Authentication=ActiveDirectoryIntegrated. Array ( @@ -104,7 +85,7 @@ Array [SQLSTATE] => IMSSP [1] => -62 [code] => -62 - [2] => Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. - [message] => Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. + [2] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported. + [message] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported. ) %s with Authentication=ActiveDirectoryPassword. diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt new file mode 100644 index 000000000..644731eb7 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt @@ -0,0 +1,88 @@ +--TEST-- +Test some error conditions of Azure AD Managed Identity support +--DESCRIPTION-- +This test expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- +"", "Authentication" => "ActiveDirectoryMsi"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty UID provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"", "Authentication" => "ActiveDirectoryMsi"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty PWD provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"pwd", "Authentication" => "ActiveDirectoryMsi"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'PWD provided'); + unset($connectionInfo); + + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + $connectionInfo = array("Authentication"=>"ActiveDirectoryMsi", "AccessToken" => "123"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'AccessToken option'); + unset($connectionInfo); +} + +function connectInvalidServer() +{ + global $server, $driver, $userName, $userPassword; + + $connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); + $conn = sqlsrv_connect($server, $connectionInfo); + if ($conn === false) { + fatalError("Failed to connect in connectInvalidServer."); + } + + $msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer']; + $version = explode(".", $msodbcsqlVer); + + if ($version[0] < 17 || $version[1] < 3) { + //skip the rest of this test, which requires ODBC driver 17.3 or above + return; + } + sqlsrv_close($conn); + + // Try connecting to an invalid server, should get an exception from ODBC + $connectionInfo = array("Authentication"=>"ActiveDirectoryMsi"); + $conn = sqlsrv_connect('invalidServer', $connectionInfo); + if ($conn) { + fatalError("AzureAD Managed Identity test: expected to fail with invalidServer\n"); + } else { + // TODO: check the exception message here, using verifyErrorMessage() + } +} + +// Test some error conditions +connectWithInvalidOptions($server); + +// Make a connection to an invalid server +connectInvalidServer(); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_data_to_str.phpt b/test/functional/sqlsrv/sqlsrv_data_to_str.phpt index 5f456108f..08f42b26b 100644 --- a/test/functional/sqlsrv/sqlsrv_data_to_str.phpt +++ b/test/functional/sqlsrv/sqlsrv_data_to_str.phpt @@ -1,7 +1,7 @@ --TEST-- large types to strings of 1MB size. --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- SQLSRV_CURSOR_CLIENT_BUFFERED)); if ($stmt === false) { printErrors(); diff --git a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt index 6b4d1100c..2c0e1b42e 100644 --- a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt +++ b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt @@ -1,5 +1,7 @@ --TEST-- -sqlsrv_has_rows() using a forward and scrollable cursor +GitHub issue #228 - how ClientBufferMaxKBSize affects sqlsrv_has_rows and sqlsrv_fetch_array +--DESCRIPTION-- +Based on the example in GitHub issue 228, configuring ClientBufferMaxKBSize with sqlsrv_configure. --SKIPIF-- --FILE-- @@ -7,6 +9,20 @@ sqlsrv_has_rows() using a forward and scrollable cursor require_once('MsCommon.inc'); +function testErrors($conn) +{ + // set client buffer size to 0KB returns false + $ret = sqlsrv_configure('ClientBufferMaxKBSize', 0); + if (!$ret) { + echo sqlsrv_errors()[0]['message'] . "\n"; + } + + $ret = sqlsrv_configure('ClientBufferMaxKBSize', -1.9); + if (!$ret) { + echo sqlsrv_errors()[0]['message'] . "\n"; + } +} + function fetchData($conn, $table, $size) { $ret = sqlsrv_configure('ClientBufferMaxKBSize', $size); @@ -16,10 +32,13 @@ function fetchData($conn, $table, $size) echo("ClientBufferMaxKBSize is $attr\n"); sqlsrv_execute($stmt); + if ($size < 2) { + echo sqlsrv_errors()[0]['message'] . "\n"; + } + $rows = sqlsrv_has_rows($stmt); var_dump($rows); - sqlsrv_execute($stmt); $numRowsFetched = 0; while ($row = sqlsrv_fetch_array($stmt)) { $numRowsFetched++; @@ -40,18 +59,17 @@ $stmt = AE\createTable($conn, $tableName1, $columns); unset($columns); $columns = array(new AE\ColumnMeta('int', 'c1_int'), - new AE\ColumnMeta('varchar(1036)', 'c2_varchar_1036')); + new AE\ColumnMeta('varchar(1400)', 'c2_varchar_1400')); $stmt = AE\createTable($conn, $tableName2, $columns); -// insert > 1KB into c2_varchar_max & c2_varchar_1036 (1036 characters). -$longString = 'This is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a test'; +// insert > 1KB into c2_varchar_max & c2_varchar_1400 (1400 characters). +$longString = str_repeat('This is a test', 100); $stmt = AE\insertRow($conn, $tableName1, array('c1_int' => 1, 'c2_varchar_max' => $longString)); -$stmt = AE\insertRow($conn, $tableName2, array('c1_int' => 1, 'c2_varchar_1036' => $longString)); +$stmt = AE\insertRow($conn, $tableName2, array('c1_int' => 1, 'c2_varchar_1400' => $longString)); +sqlsrv_free_stmt($stmt); -// set client buffer size to 0KB returns false -$ret = sqlsrv_configure('ClientBufferMaxKBSize', 0); -var_dump($ret); +testErrors($conn); // set client buffer size to 1KB $size = 1; @@ -62,19 +80,24 @@ $size = 2; fetchData($conn, $tableName1, $size); // this should return 1 row. fetchData($conn, $tableName2, $size); // this should return 1 row. -sqlsrv_free_stmt($stmt); +dropTable($conn, $tableName1); +dropTable($conn, $tableName2); + sqlsrv_close($conn); print "Done" ?> --EXPECT-- -bool(false) +Setting for ClientBufferMaxKBSize was non-int or non-positive. +Setting for ClientBufferMaxKBSize was non-int or non-positive. bool(true) ClientBufferMaxKBSize is 1 +Memory limit of 1 KB exceeded for buffered query bool(false) Number of rows fetched: 0 bool(true) ClientBufferMaxKBSize is 1 +Memory limit of 1 KB exceeded for buffered query bool(false) Number of rows fetched: 0 bool(true) diff --git a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt new file mode 100644 index 000000000..459169d93 --- /dev/null +++ b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt @@ -0,0 +1,109 @@ +--TEST-- +GitHub issue #228 - how ClientBufferMaxKBSize affects sqlsrv_has_rows and sqlsrv_fetch_array +--DESCRIPTION-- +A variation of the example in GitHub issue 228, using ClientBufferMaxKBSize the statement option. +--SKIPIF-- + +--FILE-- +"buffered", "ClientBufferMaxKBSize" => 0)); + if ($stmt !== false) { + echo("Setting client buffer size to 0KB should have failed\n"); + } else { + if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + print_r(sqlsrv_errors()); + } + } + + // set client buffer size to 0.99KB + $stmt = sqlsrv_prepare($conn, $query, array(), array("Scrollable"=>"buffered", "ClientBufferMaxKBSize" => 0.99)); + if ($stmt !== false) { + echo("Setting client buffer size to 0.99KB should have failed\n"); + } else { + if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + print_r(sqlsrv_errors()); + } + } +} + +function fetchData($conn, $table, $size) +{ + $stmt = sqlsrv_prepare($conn, "SELECT * FROM $table", array(), array("Scrollable"=>"buffered", "ClientBufferMaxKBSize" => $size)); + + $numRowsExpected = ($size > 1) ? 1 : 0; + $res = sqlsrv_execute($stmt); + if ($res && $size < 2) { + echo "Expect this to fail\n"; + } else { + $error = 'Memory limit of 1 KB exceeded for buffered query'; + if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + print_r(sqlsrv_errors()); + } + } + + $rows = sqlsrv_has_rows($stmt); + if ($numRowsExpected && !$rows) { + fatalError("sqlsrv_has_rows failed\n"); + } + + $numRowsFetched = 0; + while ($row = sqlsrv_fetch_array($stmt)) { + $numRowsFetched++; + } + if ($numRowsExpected != $numRowsFetched) { + echo("Expected $numRowsExpected but number of rows fetched is $numRowsFetched\n"); + } +} + +// connect +$conn = AE\connect(); + +$tableName1 = 'php_test_table_1'; +$tableName2 = 'php_test_table_2'; + +// Create tables +$columns = array(new AE\ColumnMeta('int', 'c1_int'), + new AE\ColumnMeta('varchar(max)', 'c2_varchar_max')); +$stmt = AE\createTable($conn, $tableName1, $columns); + +unset($columns); +$columns = array(new AE\ColumnMeta('int', 'c1_int'), + new AE\ColumnMeta('varchar(1050)', 'c2_varchar_1050')); +$stmt = AE\createTable($conn, $tableName2, $columns); + +// insert > 1KB into c2_varchar_max & c2_varchar_1050 (1050 characters). +$longString = str_repeat('This is a test', 75); + +$stmt = AE\insertRow($conn, $tableName1, array('c1_int' => 1, 'c2_varchar_max' => $longString)); +$stmt = AE\insertRow($conn, $tableName2, array('c1_int' => 1, 'c2_varchar_1050' => $longString)); +sqlsrv_free_stmt($stmt); + +$error = 'Setting for ClientBufferMaxKBSize was non-int or non-positive'; +testErrors($conn, $tableName1, $error); + +// set client buffer size to 1KB +$size = 1; +fetchData($conn, $tableName1, $size); // this should return 0 rows. +fetchData($conn, $tableName2, $size); // this should return 0 rows. +// set client buffer size to 2KB +$size = 2; +fetchData($conn, $tableName1, $size); // this should return 1 row. +fetchData($conn, $tableName2, $size); // this should return 1 row. + +dropTable($conn, $tableName1); +dropTable($conn, $tableName2); + +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/test_closeConnection.phpt b/test/functional/sqlsrv/test_closeConnection.phpt index 24c96e0d1..f796bf801 100644 --- a/test/functional/sqlsrv/test_closeConnection.phpt +++ b/test/functional/sqlsrv/test_closeConnection.phpt @@ -1,7 +1,7 @@ --TEST-- using an already closed connection. --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- >c%`KE&v->1Rwwa diff --git a/test/functional/sqlsrv/test_largeData.phpt b/test/functional/sqlsrv/test_largeData.phpt index e282df2a7..4fa2d830a 100644 --- a/test/functional/sqlsrv/test_largeData.phpt +++ b/test/functional/sqlsrv/test_largeData.phpt @@ -1,7 +1,7 @@ --TEST-- send a large amount (10MB) using encryption. --SKIPIF-- - + --FILE-- + --FILE-- ---FILE-- - "test_password", "pwd" => "! ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password2", "pwd" => "!}} ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}}" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}" )); -if ($conn) -{ - echo( "Shouldn't have connected" ); -} -$errors = sqlsrv_errors(); -echo $errors[0]["message"]; -sqlsrv_close( $conn ); - -print "Test successful"; -?> ---EXPECTREGEX-- -An unescaped right brace \(}\) was found in either the user name or password. All right braces must be escaped with another right brace \(}}\)\. -Warning: sqlsrv_close\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_non_alpha_password\.php on line 45 -Test successful diff --git a/test/functional/sqlsrv/test_sqlsrv_phptype_stream.phpt b/test/functional/sqlsrv/test_sqlsrv_phptype_stream.phpt index 5a1504200b659e81931689353de7ca16881c351b..cdf1453510012a233933bed64095435bb02bffb6 100644 GIT binary patch delta 20 bcmZn*><^sa%^9CqRa%r9pHjZj+gbwvQ^5zj delta 10 RcmeAVYzv&=y)ndE0{|Em1N#5~ diff --git a/test/functional/sqlsrv/test_stream.phpt b/test/functional/sqlsrv/test_stream.phpt index 3836341de..dee4e72e9 100644 --- a/test/functional/sqlsrv/test_stream.phpt +++ b/test/functional/sqlsrv/test_stream.phpt @@ -14,7 +14,7 @@ Test for stream zombifying. fatalError("Failed to connect."); } - $stmt = sqlsrv_query($conn, "SELECT * FROM [test_streamable_types]"); + $stmt = sqlsrv_query($conn, "SELECT * FROM sys.objects"); $metadata = sqlsrv_field_metadata($stmt); $count = count($metadata); sqlsrv_fetch($stmt); diff --git a/test/functional/sqlsrv/test_warning_errors2.phpt b/test/functional/sqlsrv/test_warning_errors2.phpt index 4145565ac..15353ac6c 100644 --- a/test/functional/sqlsrv/test_warning_errors2.phpt +++ b/test/functional/sqlsrv/test_warning_errors2.phpt @@ -15,7 +15,7 @@ if( $conn === false ) { die( print_r( sqlsrv_errors(), true )); } -$stmt = sqlsrv_prepare( $conn, "SELECT * FROM [cd_info]"); +$stmt = sqlsrv_prepare( $conn, "SELECT * FROM sys.objects"); $result = sqlsrv_field_metadata( $stmt ); if( $result === false ) { From 3ba26932d8c835080de305c35c79035f3efa593f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 19 Mar 2019 16:38:58 -0700 Subject: [PATCH 2/7] 5.6.1 hotfix (#959) --- CHANGELOG.md | 25 ++++ source/pdo_sqlsrv/pdo_stmt.cpp | 24 ++-- source/shared/version.h | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/stmt.cpp | 2 +- .../pdo_sqlsrv/pdo_937_metadata.phpt | 122 +++++++++++++++++ .../pdostatement_getColumnMeta.phpt | 3 +- ...tement_getColumnMeta_unicode_col_name.phpt | 3 +- test/functional/sqlsrv/srv_937_metadata.phpt | 125 ++++++++++++++++++ 9 files changed, 293 insertions(+), 15 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_937_metadata.phpt create mode 100644 test/functional/sqlsrv/srv_937_metadata.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 946ce626a..b7916f19b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 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: diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index cd8c697f1..773ad953a 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1020,7 +1020,7 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In // colno - The index of the field for which to return the metadata. // return_value - zval* consisting of the metadata. // Return: -// 0 for failure, 1 for success. +// FAILURE for failure, SUCCESS for success. int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value TSRMLS_DC) { PDO_RESET_STMT_ERROR; @@ -1029,20 +1029,28 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno try { SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" ); - SQLSRV_ASSERT( stmt->columns != NULL, "pdo_sqlsrv_stmt_get_col_meta: columns are not available." ); SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); - sqlsrv_malloc_auto_ptr core_meta_data; - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: stmt->driver_data was null"); - SQLSRV_ASSERT( colno >= 0 && colno < stmt->column_count, "pdo_sqlsrv_stmt_get_col_meta: invalid column number." ); + // Based on PDOStatement::getColumnMeta API, this should return FALSE + // if the requested column does not exist in the result set, or if + // no result set exists. Thus, do not use SQLSRV_ASSERT, which causes + // the script to fail right away. Instead, log this warning if logging + // is enabled + if (colno < 0 || colno >= stmt->column_count || stmt->columns == NULL) { + LOG( SEV_WARNING, "Invalid column number %1!d!", colno ); + return FAILURE; + } - core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); // initialize the array to nothing, as PDO requires us to create it core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + sqlsrv_malloc_auto_ptr core_meta_data; + + core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + // add the following fields: flags, native_type, driver:decl_type, table add_assoc_long( return_value, "flags", 0 ); @@ -1088,14 +1096,14 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } catch( core::CoreException& ) { - return 0; + return FAILURE; } catch(...) { DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); } - return 1; + return SUCCESS; } diff --git a/source/shared/version.h b/source/shared/version.h index c7c554813..7424d9001 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -27,7 +27,7 @@ // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 #define SQLVERSION_MINOR 6 -#define SQLVERSION_PATCH 0 +#define SQLVERSION_PATCH 1 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 5a2477e99..887d1e775 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -27,7 +27,7 @@ if( PHP_SQLSRV != "no" ) { 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_PDO_SQLSRV == "no" || PHP_SQLSRV_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"); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index c366e0305..9d60de25f 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1807,7 +1807,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) throw; } - SQLSRV_ASSERT(num_cols > 0 && stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); + SQLSRV_ASSERT(stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); return num_cols; } diff --git a/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt b/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt new file mode 100644 index 000000000..d0cad7e75 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt @@ -0,0 +1,122 @@ +--TEST-- +GitHub issue 937 - getting metadata will not fail after an UPDATE / DELETE statement +--DESCRIPTION-- +Verifies that getColumnMeta will not fail after processing an UPDATE / DELETE query that returns no fields. Instead, it should simply return FALSE because no result set exists. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +getColumnMeta(0); + if ($metadata !== FALSE) { + echo "Expects FALSE because no result set exists!\n"; + } +} + +try { + $conn = connect(); + + dropTable($conn, $tableName); + dropProc($conn, $procName); + + $tsql = "CREATE TABLE $tableName([id] [int] NOT NULL, [name] [varchar](10) NOT NULL)"; + $conn->query($tsql); + + $id = 3; + $tsql = "INSERT INTO $tableName VALUES ($id, 'abcde')"; + $conn->query($tsql); + + $tsql = "UPDATE $tableName SET name = 'updated' WHERE id = $id"; + $stmt = $conn->prepare($tsql); + $stmt->execute(); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after UPDATE: $numCol\n"; + checkMetaData($stmt); + + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->query($tsql); + $numCol = $metadata = $stmt->columnCount(); + for ($i = 0; $i < $numCol; $i++) { + $metadata = $stmt->getColumnMeta($i); + var_dump($metadata); + } + + createProc($conn, $procName, "@id int, @val varchar(10) OUTPUT", "SELECT @val = name FROM $tableName WHERE id = @id"); + + $value = ''; + $tsql = "{CALL [$procName] (?, ?)}"; + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $id, PDO::PARAM_INT); + $stmt->bindParam(2, $value, PDO::PARAM_STR, 10); + $stmt->execute(); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after PROCEDURE: $numCol\n"; + echo "Value returned: $value\n"; + checkMetaData($stmt); + + $query = "DELETE FROM $tableName WHERE name = 'updated'"; + $stmt = $conn->query($query); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after DELETE: $numCol\n"; + checkMetaData($stmt); +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} + +dropTable($conn, $tableName); +dropProc($conn, $procName); + +unset($stmt); +unset($conn); + +?> +--EXPECT-- +Number of columns after UPDATE: 0 +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(3) "int" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(2) "id" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(7) "varchar" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(4) "name" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} +Number of columns after PROCEDURE: 0 +Value returned: updated +Number of columns after DELETE: 0 diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt index f42d1d3d3..40805dc23 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt @@ -220,5 +220,4 @@ array(7) { Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x bool(false) - -Fatal error: pdo_sqlsrv_stmt_get_col_meta: invalid column number. in %s on line %x +bool(false) diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt index bb01be10a..e7075ee46 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt @@ -259,5 +259,4 @@ array(7) { Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x bool(false) - -Fatal error: pdo_sqlsrv_stmt_get_col_meta: invalid column number. in %s on line %x +bool(false) \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_937_metadata.phpt b/test/functional/sqlsrv/srv_937_metadata.phpt new file mode 100644 index 000000000..47c9452fe --- /dev/null +++ b/test/functional/sqlsrv/srv_937_metadata.phpt @@ -0,0 +1,125 @@ +--TEST-- +GitHub issue #937 - getting metadata will not fail after an UPDATE / DELETE statement +--DESCRIPTION-- +Verifies that sqlsrv_field_metadata will return an empty array after processing an +UPDATE / DELETE query that returns no fields. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "buffered"); +$tsql = "DELETE FROM $tableName WHERE dummyColumn = 'updated'"; +$stmt = sqlsrv_query($conn, $tsql, array(), $options); +$fieldmeta = sqlsrv_field_metadata($stmt); +var_dump($fieldmeta); + +dropTable($conn, $tableName); +dropProc($conn, $procName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +array(2) { + [0]=> + array(6) { + ["Name"]=> + string(2) "id" + ["Type"]=> + int(4) + ["Size"]=> + NULL + ["Precision"]=> + int(10) + ["Scale"]=> + NULL + ["Nullable"]=> + int(0) + } + [1]=> + array(6) { + ["Name"]=> + string(11) "dummyColumn" + ["Type"]=> + int(12) + ["Size"]=> + int(10) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(0) + } +} +array(0) { +} +The value returned: updated +array(0) { +} +array(0) { +} \ No newline at end of file From 5dabd47e41bf4f863f8c56197c213a4e76d3ea25 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 9 May 2019 14:54:24 -0700 Subject: [PATCH 3/7] Updated links and versions (#987) --- Linux-mac-install.md | 12 ++++++------ README.md | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 2a7357f47..e34f07b06 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -9,7 +9,7 @@ These instructions install PHP 7.3 by default. Note that some supported Linux di - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) -- [Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) +- [Installing the drivers on macOS Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-sierra-high-sierra-and-mojave) ## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10 @@ -38,7 +38,7 @@ exit ### Step 4. Install Apache and configure driver loading ``` sudo su -apt-get install libapache2-mod-php7.2 apache2 +apt-get install libapache2-mod-php7.3 apache2 a2dismod mpm_event a2enmod mpm_prefork a2enmod php7.3 @@ -91,8 +91,8 @@ exit An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` pecl download sqlsrv -tar xvzf sqlsrv-5.6.0.tgz -cd sqlsrv-5.6.0/ +tar xvzf sqlsrv-5.6.1.tgz +cd sqlsrv-5.6.1/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -214,7 +214,7 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave +## Installing the drivers on macOS Sierra, High Sierra, and Mojave If you do not already have it, install brew as follows: ``` @@ -259,7 +259,7 @@ apachectl -V | grep SERVER_CONFIG_FILE ``` and substitute the path for `httpd.conf` in the following commands: ``` -echo "LoadModule php7_module /usr/local/opt/php@7.2/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf +echo "LoadModule php7_module /usr/local/opt/php@7.3/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf (echo ""; echo "SetHandler application/x-httpd-php"; echo "";) >> /usr/local/etc/httpd/httpd.conf ``` ### Step 5. Restart Apache and test the sample script diff --git a/README.md b/README.md index 84952fe35..67183a900 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ Thank you for taking the time to participate in our last survey. You can continu [av-image]: https://ci.appveyor.com/api/projects/status/vo4rfei6lxlamrnc?svg=true [av-site]: https://ci.appveyor.com/project/msphpsql/msphpsql/branch/dev -[tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev -[tv-site]: https://travis-ci.org/Microsoft/msphpsql/ -[Coverage Coveralls]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev -[coveralls-site]: https://coveralls.io/github/Microsoft/msphpsql?branch=dev +[tv-image]: https://travis-ci.org/microsoft/msphpsql.svg?branch=dev +[tv-site]: https://travis-ci.org/microsoft/msphpsql/ +[Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev +[coveralls-site]: https://coveralls.io/github/microsoft/msphpsql?branch=dev [Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg [codecov-site]: https://codecov.io/gh/microsoft/msphpsql From 160a6909041853e38283214d6495d0ff6fdc12ed Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Sat, 23 Nov 2019 00:03:57 -0800 Subject: [PATCH 4/7] Fixed AKV keyword tests for AE v2 --- .../pdo_ae_azure_key_vault_keywords.phpt | 22 ++++++++++++- .../sqlsrv_ae_azure_key_vault_keywords.phpt | 31 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index cc49693bd..14b1ed271 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -48,6 +48,24 @@ $dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nva $tableName = "akv_comparison_table"; +// First determine if the server is AE v2 enabled +$isEnclaveEnabled = false; +$connectionOptions = "sqlsrv:Server=$server;Database=$databaseName"; + +$conn = new PDO($connectionOptions, $uid, $pwd); +if (!$conn) { + fatalError("Initial connection failed\n"); +} else { + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = $conn->query($query); + $info = $stmt->fetch(); + if ($info['value'] == 1 and $info['value_in_use'] == 1) { + $isEnclaveEnabled = true; + } +} + +unset($conn); + // Test every combination of the keywords above. // Leave out good credentials to ensure that caching does not influence the // results. The cache timeout can only be changed with SQLSetConnectAttr, so @@ -118,7 +136,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { } else { // The INSERT query succeeded with bad credentials, which // should only happen when encryption is not enabled. - if (isColEncrypted()) { + if (!(!isColEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); } } @@ -135,6 +153,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { $errors, array('CE258', '0'), array('CE275', '0'), + array('CE400', '0'), array('IMSSP', '-85'), array('IMSSP', '-86'), array('IMSSP', '-87'), @@ -147,6 +166,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { $errors, array('CE258', '0'), array('CE275', '0'), + array('CE400', '0'), array('IMSSP', '-85'), array('IMSSP', '-86'), array('IMSSP', '-87'), diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt index 3734e0be0..259e97746 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt @@ -47,6 +47,28 @@ $dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nva $tableName = "akv_comparison_table"; +// First determine if the server is AE v2 enabled +$isEnclaveEnabled = false; +$connectionOptions = array("CharacterSet"=>"UTF-8", + "database"=>$databaseName, + "uid"=>$uid, + "pwd"=>$pwd, + "ConnectionPooling"=>0); + +$conn = sqlsrv_connect($server, $connectionOptions); +if (!$conn) { + fatalError("Initial connection failed\n"); +} else { + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = sqlsrv_query($conn, $query); + $info = sqlsrv_fetch_array($stmt); + if ($info['value'] == 1 and $info['value_in_use'] == 1) { + $isEnclaveEnabled = true; + } +} + +unset($conn); + // Test every combination of the keywords above. // Leave out good credentials to ensure that caching does not influence the // results. The cache timeout can only be changed with SQLSetConnectAttr, so @@ -96,7 +118,8 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { array('IMSSP','-110'), array('IMSSP','-111'), array('IMSSP','-112'), - array('IMSSP','-113') + array('IMSSP','-113'), + array('CE400','0') ); } else { $columns = array(); @@ -148,8 +171,10 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { sqlsrv_free_stmt($stmt); } else { // The INSERT query succeeded with bad credentials, which - // should only happen when encryption is not enabled. - if (AE\isDataEncrypted()) { + // should only happen when 1. encryption is not enabled or + // 2. when ColumnEncryption is set to something other than + // enabled or disabled and the server is not enclave-enabled + if (!(!AE\isDataEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); } } From 53c6d91e8fb193ec87af0fb507f37b48a3917b32 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 25 Nov 2019 11:50:16 -0800 Subject: [PATCH 5/7] Added comment --- .../pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt | 5 ++++- .../sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index 14b1ed271..2ecf64a40 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -135,7 +135,10 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { unset($stmt); } else { // The INSERT query succeeded with bad credentials, which - // should only happen when encryption is not enabled. + // should only happen when 1. encryption is not enabled or + // 2. when ColumnEncryption is set to something other than + // enabled or disabled (i.e. $1 == 2), and the server is + // not enclave-enabled if (!(!isColEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); } diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt index 259e97746..2847fc755 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt @@ -173,7 +173,8 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { // The INSERT query succeeded with bad credentials, which // should only happen when 1. encryption is not enabled or // 2. when ColumnEncryption is set to something other than - // enabled or disabled and the server is not enclave-enabled + // enabled or disabled (i.e. $1 == 2), and the server is + // not enclave-enabled if (!(!AE\isDataEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); } From 871617c3fbc5cd3607fcea07d730d9363b1e8723 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 25 Nov 2019 13:53:09 -0800 Subject: [PATCH 6/7] Free proc cache before starting test --- test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt | 2 ++ test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index 2ecf64a40..1f20150ad 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -62,6 +62,8 @@ if (!$conn) { if ($info['value'] == 1 and $info['value_in_use'] == 1) { $isEnclaveEnabled = true; } + + $conn->query("DBCC FREEPROCCACHE"); } unset($conn); diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt index 2847fc755..6354ac060 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt @@ -65,6 +65,8 @@ if (!$conn) { if ($info['value'] == 1 and $info['value_in_use'] == 1) { $isEnclaveEnabled = true; } + + sqlsrv_query($conn, "DBCC FREEPROCCACHE"); } unset($conn); From ff90b3f68395b3ff3a94b691b2d894426470bdc0 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 25 Nov 2019 14:02:26 -0800 Subject: [PATCH 7/7] Fixed comment --- test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt | 2 +- test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index 1f20150ad..2e22a203a 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -139,7 +139,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { // The INSERT query succeeded with bad credentials, which // should only happen when 1. encryption is not enabled or // 2. when ColumnEncryption is set to something other than - // enabled or disabled (i.e. $1 == 2), and the server is + // enabled or disabled (i.e. $i == 2), and the server is // not enclave-enabled if (!(!isColEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt index 6354ac060..e6f03d279 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt @@ -175,7 +175,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { // The INSERT query succeeded with bad credentials, which // should only happen when 1. encryption is not enabled or // 2. when ColumnEncryption is set to something other than - // enabled or disabled (i.e. $1 == 2), and the server is + // enabled or disabled (i.e. $i == 2), and the server is // not enclave-enabled if (!(!AE\isDataEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n");