From 9654020d67aeae672e3d4c71496f6ae8d1f0fe2c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 20 Jul 2018 17:24:48 -0700 Subject: [PATCH 1/7] Dev (#820) * Fixed the potential error reported by Prefast code analysis * Use SQLSRV_ASSERT for checking NULL ptrs * For these AKV tests check env despite not AE connected * Added the driver option to run functional tests * Fixed connection pooling tests for more than one ODBC drivers * added driver option to pdo isPooled.php * Removed win32 ifdefs re connection resiliency (#802) * Set the driver argument for getDSN to null by default (#798) * Added the driver argument to getDSN * Dropped the driver argument but set to null as default * Removed the AE condition in locale support * Modified the AE condition for locale support * Changed int to SQLLEN to avoid infinite loop (#806) * Version 5.3.0 (#803) * Version 5.3.0 * Fixed the wrong replacements * Added comments block to m4 files * Use dnl for comments * Modified AE fetch phptypes test to insert only one row at a time and loop through php types (#801) * Modified AE fetch phptypes test to insert only one row at a time and loop through php types * Fixed formatting * Streamlined two very similar large column name tests (#807) * Streamlined two very similar large column name tests * Changed the EOL * Updates to change log and readme (#811) * Updates to change log and readme * Dropped support for Ubuntu 17 * Modified as per review comments * Fixed connection resiliency tests for Unix, updated AppVeyor for ODBC 17.2 * Fixed expected output * Fixed output and skipifs * Fixed skipifs and output * Fixed driver name * Updated installation instructions and sample script (#813) * Updated instructions and sample test for 5.3.0 RTW * Fixed sample code to adhere to php coding standard * Fixed cases and spaces * Modified NOTE for UB 18.04 based on review comments * Added 'exit' * Modified change log and readme based on review to PR 811 * Applied review comments * build output to debug appveyor failure * removed debug output * Streamlined two very similar large column name tests (#815) * Streamlined two very similar large column name tests * Added random number of test table names to avoid operand clash issues * Replaced to with for based on review * Changelog updated * changelog updated, test skipif changed to run on unix platforms * Fixed skipif typo * Fixed typo in skipif for pdo * Fixed some output for Travis * Moved error checking inside pdo connres tests * Added links back to changelog * Fixed output for sqlsrv connres tests * Fixed output * Fixed output again --- CHANGELOG.md | 35 ++++ LICENSE | 26 +-- Linux-mac-install.md | 70 +++---- README.md | 20 +- appveyor.yml | 6 +- source/pdo_sqlsrv/config.m4 | 20 ++ source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 12 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- 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 | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 7 +- source/shared/core_sqlsrv.h | 6 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 6 +- 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 | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 8 +- 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 | 20 ++ source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 8 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- test/functional/pdo_sqlsrv/MsCommon.inc | 158 +--------------- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 11 +- test/functional/pdo_sqlsrv/MsSetup.inc | 4 +- .../pdo_sqlsrv/PDO101_LargeColumnName.phpt | 95 +++++----- ...O101_LargeColumnName_unicode_col_name.phpt | 65 +++---- .../pdo_sqlsrv/PDO21_Connection.phpt | 2 +- .../pdo_sqlsrv/PDO_ConnPool_Unix.phpt | 13 +- test/functional/pdo_sqlsrv/isPooled.php | 4 +- .../pdo_sqlsrv/pdo_020_bind_params_array.phpt | 2 + .../pdo_sqlsrv/pdo_040_error_information.phpt | 2 + .../pdo_065_construct_persistent.phpt | 2 +- .../pdo_065_construct_prefetch.phpt | 2 +- .../pdo_707_ae_output_param_decimals.phpt | 2 +- .../pdo_ae_azure_key_vault_client_secret.phpt | 2 +- .../pdo_ae_azure_key_vault_keywords.phpt | 2 +- ..._ae_azure_key_vault_username_password.phpt | 2 +- .../pdo_sqlsrv/pdo_ae_output_param_all.phpt | 1 + .../pdo_sqlsrv/pdo_connect_encrypted.phpt | 2 +- .../pdo_sqlsrv/pdo_connection_resiliency.phpt | 16 +- .../pdo_connection_resiliency_keywords.phpt | 13 +- ...onnection_resiliency_prepare_transact.phpt | 18 +- .../pdo_connection_resiliency_timeouts.phpt | 9 +- test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt | 3 +- test/functional/pdo_sqlsrv/skipif_not_akv.inc | 20 ++ .../pdo_sqlsrv/skipif_protocol_not_tcp.inc | 2 - .../skipif_version_less_than_2k14.inc | 37 +++- .../skipif_version_less_than_2k16.inc | 37 +++- .../pdo_sqlsrv/test_ae_keys_setup.phpt | 83 +++++---- test/functional/sqlsrv/MsCommon.inc | 2 +- test/functional/sqlsrv/MsSetup.inc | 4 +- test/functional/sqlsrv/TC52_StreamSend.phpt | 2 +- .../sqlsrv/TC54_StreamPrepared.phpt | 2 +- .../sqlsrv/TC84_LargeColumnName.phpt | 49 ++--- .../sqlsrv/TC84_LargeColumnName_unicode.phpt | 51 ++--- ...TC84_LargeColumnName_unicode_col_name.phpt | 75 -------- .../sqlsrv/connection_resiliency.phpt | 64 ++----- .../connection_resiliency_keywords.phpt | 15 +- ...onnection_resiliency_prepare_transact.phpt | 34 +--- .../connection_resiliency_timeouts.phpt | 32 +--- test/functional/sqlsrv/skipif_not_akv.inc | 21 +++ .../sqlsrv/skipif_protocol_not_tcp.inc | 2 - .../sqlsrv/skipif_version_less_than_2k14.inc | 37 +++- .../sqlsrv/skipif_version_less_than_2k16.inc | 36 +++- .../sqlsrv/sqlsrv_ConnPool_Unix.phpt | 13 +- ...lsrv_ae_azure_key_vault_client_secret.phpt | 2 +- .../sqlsrv_ae_azure_key_vault_keywords.phpt | 2 +- ..._ae_azure_key_vault_username_password.phpt | 2 +- .../sqlsrv/sqlsrv_ae_fetch_phptypes.phpt | 174 ++++++++++-------- .../sqlsrv/sqlsrv_connect_encrypted.phpt | 2 +- .../functional/sqlsrv/test_ae_keys_setup.phpt | 39 ++-- 97 files changed, 765 insertions(+), 816 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/skipif_not_akv.inc delete mode 100644 test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt create mode 100644 test/functional/sqlsrv/skipif_not_akv.inc diff --git a/CHANGELOG.md b/CHANGELOG.md index 765bb786c..abaa24db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.3.0 - 2018-07-20 +Updated PECL release packages. Here is the list of updates: + +### Added +- Added support for Azure Key Vault for Always Encrypted functionality. Always Encrypted functionality is supported on Linux and macOS through Azure Key Vault +- Added support for connection resiliency on Linux and macOS (requires version 17.2 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) +- Added support for macOS High Sierra (requires version 17 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) +- Added support for Ubuntu 18.04 (requires version 17.2 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) + +### Fixed +- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connections (fixed in MS ODBC Driver 17.1) +- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling (fixed in MS ODBC Driver 17.1) +- Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameters fails when the query in the stored procedure returns no data. The test case has been added to the test lab. +- Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation +- Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38) +- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers +- Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO::lastInsertId so that data types other than integers can be supported +- Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY +- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC + +### Limitations +- No support for inout / output params when using sql_variant type +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/en-us/sql/connect/php/using-always-encrypted-php-drivers?view=sql-server-2017#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.6 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) + ## 5.2.1-preview - 2018-06-01 Updated PECL release packages. Here is the list of updates: diff --git a/LICENSE b/LICENSE index b52d9d433..13fab6115 100644 --- a/LICENSE +++ b/LICENSE @@ -1,14 +1,14 @@ -Copyright(c) 2017 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 +Copyright(c) 2018 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. \ No newline at end of file diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 6964ca0e2..d84eca655 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,20 +1,24 @@ -# PHP Linux and Mac Drivers Installation Tutorial -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 Microsoft SQL Server on Ubuntu 16.04 and 17.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11 and 10.12. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for Microsoft 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 Microsoft 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). +# 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 Microsoft SQL Server on Ubuntu 16.04, 17.10 and 18.04, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12 and 10.13. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for Microsoft 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 Microsoft 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 instruction 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.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04 and 17.10](#installing-the-drivers-on-ubuntu-1604-and-1710) +- [Installing the drivers on Ubuntu 16.04, 17.10, and 18.04](#installing-the-drivers-on-ubuntu-1604-1710-and-1804) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) - [Installing the drivers on macOS El Capitan, Sierra and High Sierra](#installing-the-drivers-on-macos-el-capitan-sierra-and-high-sierra) -## Installing the drivers on Ubuntu 16.04 and 17.10 +## Installing the drivers on Ubuntu 16.04, 17.10 and 18.04 > [!NOTE] > To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands. +> For Ubuntu 18.04, the step to add the ondrej repository is not required unless +> PHP 7.0 or 7.1 is needed. However, installing PHP 7.0 or 7.1 in Ubuntu 18.04 may +> not work as packages from the ondrej repository come with dependencies that may +> conflict with a base Ubuntu 18.04 install. ### Step 1. Install PHP ``` @@ -44,6 +48,7 @@ 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 +exit ``` ### Step 5. Restart Apache and test the sample script ``` @@ -86,11 +91,11 @@ echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini file echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini 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: +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.2.0.tgz -cd sqlsrv-5.2.0/ +tar xvzf sqlsrv-5.3.0.tgz +cd sqlsrv-5.3.0/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -196,6 +201,7 @@ zypper install apache2 apache2-mod_php7 a2enmod php7 echo "extension=sqlsrv.so" >> /etc/php7/apache2/php.ini echo "extension=pdo_sqlsrv.so" >> /etc/php7/apache2/php.ini +exit ``` ### Step 5. Restart Apache and test the sample script ``` @@ -266,47 +272,47 @@ To test this sample script, create a file called testsql.php in your system's do "yourDatabase", - "Uid" => "yourUsername", - "PWD" => "yourPassword" + "database" => "yourDatabase", + "uid" => "yourUsername", + "pwd" => "yourPassword" ); -//Establishes the connection +// Establishes the connection $conn = sqlsrv_connect($serverName, $connectionOptions); -if( $conn === false ) { - die( FormatErrors( sqlsrv_errors())); +if ($conn === false) { + die(formatErrors(sqlsrv_errors())); } -//Select Query -$tsql= "SELECT @@Version as SQL_VERSION"; +// Select Query +$tsql = "SELECT @@Version AS SQL_VERSION"; -//Executes the query -$getResults= sqlsrv_query($conn, $tsql); +// Executes the query +$stmt = sqlsrv_query($conn, $tsql); -//Error handling -if ($getResults == FALSE) - die(FormatErrors(sqlsrv_errors())); +// Error handling +if ($stmt === false) { + die(formatErrors(sqlsrv_errors())); +} ?>

Results :

"); +while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { + echo $row['SQL_VERSION'] . PHP_EOL; } -sqlsrv_free_stmt($getResults); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); -function FormatErrors( $errors ) +function formatErrors($errors) { - /* Display errors. */ + // Display errors echo "Error information:
"; - foreach ( $errors as $error ) - { - echo "SQLSTATE: ".$error['SQLSTATE']."
"; - echo "Code: ".$error['code']."
"; - echo "Message: ".$error['message']."
"; + foreach ($errors as $error) { + echo "SQLSTATE: ". $error['SQLSTATE'] . "
"; + echo "Code: ". $error['code'] . "
"; + echo "Message: ". $error['message'] . "
"; } } ?> diff --git a/README.md b/README.md index b30a3fa0f..20e3c0e98 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ For full details on the system requirements for the drivers, see the [system req On the client machine: - PHP 7.0.x, 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) - A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP -- [Microsoft ODBC Driver 17][odbc17], [Microsoft ODBC Driver 13][odbc13], or [Microsoft ODBC Driver 11][odbc11] +- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. @@ -86,14 +86,14 @@ The version number may have trailing pre-release version identifiers to indicate - Build metadata may be denoted by a plus sign followed by 4 or 5 digits, such as `1.2.3-preview+5678` or `1.2.3+5678`. Build metadata does not figure into the precedence order. ## Future Plans -- Expand SQL Server 2016 feature support (example: Always Encrypted) +- Expand SQL Server 2016 feature support (example: Azure Active Directory) - Add more verification/fundamental tests - Bug fixes ## Guidelines for Reporting Issues We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you: -- First check the [FAQ](https://github.com/Microsoft/msphpsql/wiki/FAQ) +- First check the [FAQ](https://github.com/Microsoft/msphpsql/wiki/FAQ) for common problems - Report each issue as a new issue (but check first if it's already been reported) - Please address the questions in the new issue template and provide scripts, table schema, and/or any details that may help reproduce the problem(s) @@ -106,15 +106,15 @@ Thank you! **Q:** What's next? -**A:** On March 23, 2018 we released the production release version 5.2.0 of our PHP Driver. We will continue working on our future plans and releasing previews of upcoming releases frequently. +**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. **Q:** Is Microsoft taking pull requests for this project? -**A:** Yes. Please submit pull requests to the **dev** branch and not the **master** branch. +**A:** Yes. Please submit pull requests to the **dev** branch, not the **master** branch. ## License -The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. +The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. ## Code of conduct @@ -138,12 +138,6 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf [phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild -[phpdoc]: http://msdn.microsoft.com/library/dd903047%28SQL.11%29.aspx - -[odbc11]: https://www.microsoft.com/download/details.aspx?id=36434 - -[odbc13]: https://www.microsoft.com/download/details.aspx?id=50420 - -[odbc17]: https://www.microsoft.com/download/details.aspx?id=56567 +[phpdoc]: https://docs.microsoft.com/en-us/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 [PHPMan]: http://php.net/manual/install.unix.php diff --git a/appveyor.yml b/appveyor.yml index 9d2cdeea1..4a0fcbddd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -81,10 +81,10 @@ install: } Else { $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; } - - echo Downloading MSODBCSQL 17.1 + - echo Downloading MSODBCSQL 17.2 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/msodbcsql_17.1.0.1_x64.msi', 'c:\projects\msodbcsql_17.1.0.1_x64.msi') - - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.1.0.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.2.0.1_x64.msi', 'c:\projects\msodbcsql_17.2.0.1_x64.msi') + - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.2.0.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" - dir %WINDIR%\System32\msodbcsql*.dll diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 46e22960d..b1bfd6e48 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -1,3 +1,23 @@ +dnl ---------------------------------------------------------------------------------------------------------------------------------- +dnl File: config.m4 +dnl +dnl Contents: the code that will go into the configure script, indicating options, +dnl external libraries and includes, and what source files are to be compiled. +dnl +dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Copyright(c) Microsoft Corporation +dnl All rights reserved. +dnl MIT License +dnl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +dnl to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +dnl and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +dnl The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +dnl THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +dnl IN THE SOFTWARE. +dnl --------------------------------------------------------------------------------------------------------------------------------- + PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support, [ --with-pdo_sqlsrv Include pdo_sqlsrv support]) diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index bd1eaf048..7066e6254 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 48ed3b123..330205565 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -43,10 +43,8 @@ const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectionPooling[] = "ConnectionPooling"; -#ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; -#endif // _WIN32 const char Database[] = "Database"; const char Driver[] = "Driver"; const char Encrypt[] = "Encrypt"; @@ -109,7 +107,6 @@ struct pdo_txn_isolation_conn_attr_func static void func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); }; -#ifdef _WIN32 struct pdo_int_conn_str_func { static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) @@ -125,7 +122,6 @@ struct pdo_int_conn_str_func { conn_str += "};"; } }; -#endif // _WIN32 template struct pdo_int_conn_attr_func { @@ -243,7 +239,6 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_STRING, column_encryption_set_func::func }, -#ifdef _WIN32 { PDOConnOptionNames::ConnectRetryCount, sizeof( PDOConnOptionNames::ConnectRetryCount ), @@ -262,7 +257,6 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_INT, pdo_int_conn_str_func::func }, -#endif // _WIN32 { PDOConnOptionNames::Database, sizeof( PDOConnOptionNames::Database ), @@ -1276,7 +1270,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, try { - char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; + char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ] = {'\0'}; if( name == NULL ) { strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); } @@ -1300,7 +1294,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, sqlsrv_malloc_auto_ptr wsql_string; unsigned int wsql_len; - wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast( last_insert_id_query ), static_cast( strnlen_s( last_insert_id_query )), &wsql_len ); + wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast( last_insert_id_query ), sizeof(last_insert_id_query), &wsql_len ); CHECK_CUSTOM_ERROR( wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { throw core::CoreException(); diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 0fa961c6e..65e7a0786 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 8c783b39f..baafeed73 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index fd65b9eac..d41dde3b7 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 343537dd6..a05e75471 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index ba7dd4b76..38e4cec42 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 248c99ccc..8b94ddfad 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 6a19a4259..1b48c0e92 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 61080a424..87f15f463 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 550c0d359..d5183fc52 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 a0b31b783..b12e789b7 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 7f2d16eb5..b094e110b 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index d47080066..cda38fa55 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index d5882f2c6..62a757d86 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -345,7 +345,8 @@ sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ]; SQLINTEGER native_code; SQLSMALLINT wnative_message_len = 0; - + + SQLSRV_ASSERT(odbc != NULL, "odbc_get_diag_rec: sqlsrv_stmt* odbc was null."); SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, SQL_MAX_ERROR_MESSAGE_LENGTH + 1, &wnative_message_len ); if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { @@ -963,7 +964,7 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) // to get the number of hex digits we can copy SQLLEN to_copy_hex = to_copy / (2 * extra); - for( int i = 0; i < to_copy_hex; ++i ) { + for( SQLLEN i = 0; i < to_copy_hex; ++i ) { *h = hex_chars[ (*b & 0xf0) >> 4 ]; h++; *h = hex_chars[ (*b++ & 0x0f) ]; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 39e53b19a..5530a5540 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -1112,10 +1112,8 @@ const char Driver[] = "Driver"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; const char ColumnEncryption[] = "ColumnEncryption"; -#ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; -#endif // _WIN32 const char Database[] = "Database"; const char Encrypt[] = "Encrypt"; const char Failover_Partner[] = "Failover_Partner"; @@ -1168,10 +1166,8 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID, SQLSRV_CONN_OPTION_KEYSTORE_SECRET, SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION, -#ifdef _WIN32 SQLSRV_CONN_OPTION_CONN_RETRY_COUNT, SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL, -#endif // _WIN32 // Driver specific connection options SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 2cdb7a003..6d4d4f613 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 5309f0043..32780a5b2 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,7 +26,7 @@ namespace { int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ TSRMLS_DC ) { sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); + SQLSRV_ASSERT( ss != NULL && ss->stmt != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); // free the stream resources in the Zend engine php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); @@ -52,7 +52,7 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) sqlsrv_malloc_auto_ptr temp_buf; sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); + SQLSRV_ASSERT( ss != NULL && ss->stmt != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); try { diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index c537f766e..d8b7b2445 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 486a381d2..88e8d1a40 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 39e615f48..cc0163f0f 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 7f5a974aa..e8c8e5bb2 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 6c88f0e59..bf2bc9ca2 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 136c5a519..2ec13e09d 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 92221e984..75251eb6d 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index d6b74a5b8..3a759252e 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 7be9ba68a..78478eaa1 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 6daf7fcd6..dc3b4ca5b 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 140e24484..7d6554431 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 2 -#define SQLVERSION_PATCH 1 +#define SQLVERSION_MINOR 3 +#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 f768f112f..baa393e22 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 68595afb9..1baa473a7 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 292960694..44eb7c844 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 3b1cbc68b..36aceae16 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 e03ab0f4c..6b4190665 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -1,3 +1,23 @@ +dnl ---------------------------------------------------------------------------------------------------------------------------------- +dnl File: config.m4 +dnl +dnl Contents: the code that will go into the configure script, indicating options, +dnl external libraries and includes, and what source files are to be compiled. +dnl +dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Copyright(c) Microsoft Corporation +dnl All rights reserved. +dnl MIT License +dnl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +dnl to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +dnl and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +dnl The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +dnl THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +dnl IN THE SOFTWARE. +dnl --------------------------------------------------------------------------------------------------------------------------------- + PHP_ARG_ENABLE(sqlsrv, whether to enable sqlsrv functions, [ --disable-sqlsrv Disable sqlsrv functions], yes) diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 77d8c4d11..449789c4b 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 183ac3c55..7bf805089 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -95,7 +95,6 @@ struct bool_conn_str_func { } }; -#ifdef _WIN32 struct int_conn_str_func { static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) @@ -111,7 +110,6 @@ struct int_conn_str_func { conn_str += "};"; } }; -#endif // _WIN32 template struct int_conn_attr_func { @@ -188,10 +186,8 @@ const char Authentication[] = "Authentication"; const char CharacterSet[] = "CharacterSet"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectionPooling[] = "ConnectionPooling"; -#ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; -#endif // _WIN32 const char Database[] = "Database"; const char DateAsString[] = "ReturnDatesAsStrings"; const char Driver[] = "Driver"; @@ -324,7 +320,6 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, column_encryption_set_func::func }, -#ifdef _WIN32 { SSConnOptionNames::ConnectRetryCount, sizeof( SSConnOptionNames::ConnectRetryCount ), @@ -343,7 +338,6 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_INT, int_conn_str_func::func }, -#endif // _WIN32 { SSConnOptionNames::Database, sizeof( SSConnOptionNames::Database ), diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 7385dc6b1..bb8ab04b4 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 17175730a..6c5e7b015 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 96aeae8a0..ac334cb9b 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 446f50d88..ffee85372 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 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 50b963e48..bc26658c0 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.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/test/functional/pdo_sqlsrv/MsCommon.inc b/test/functional/pdo_sqlsrv/MsCommon.inc index cddfdab9c..b1c479e95 100644 --- a/test/functional/pdo_sqlsrv/MsCommon.inc +++ b/test/functional/pdo_sqlsrv/MsCommon.inc @@ -8,30 +8,6 @@ */ -// -// looks like an additional file (in addition to pdo_test_base.inc) may be needed for these PHPTs -// to be runnable from the MSSQL teams' internal proprietary test running system -// - -function IsAEQualified($conn) -{ - $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; - $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; - if ($msodbcsql_maj < 17) { - return false; - } - require 'MsSetup.inc'; - if ($daasMode) { - // running against Azure - return true; - } - // if not Azure, check the server version - $server_ver = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); - if (explode('.', $server_ver)[0] < 13) - return false; - return true; -} - // TO BE DELETED function connect($options=array()) { @@ -40,7 +16,7 @@ function connect($options=array()) // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases require 'MsSetup.inc'; - $conn = new PDO( "sqlsrv:Server=$server;database=$databaseName;ConnectionPooling=false;" , $uid, $pwd, $options); + $conn = new PDO( "sqlsrv:Server=$server;database=$databaseName;Driver=$driver;ConnectionPooling=false;" , $uid, $pwd, $options); $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); return $conn; } @@ -58,138 +34,6 @@ function connect($options=array()) } } - -/** - * Connect to the database specified in MsSetup.inc; Column Encryption keywords automatically added when $keystore is not none - * @param string $keywords : string to append to the dsn string in PDO::_construct - * @param array $options : attributes to pass to PDO::_construct - * @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none - * for testing fetching encrypted data when connection column encryption is off - * @return PDO connection object - */ -function ae_connect( $keywords='', $options=array(), $disableCE = false ) -{ - try - { - // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, - // which does not support switching databases - require 'MsSetup.inc'; - $dsn = "sqlsrv:Server=$server;database=$databaseName;ConnectionPooling=false;"; - if ( $keystore != "none" && !$disableCE ) - { - $dsn .= "ColumnEncryption=Enabled;"; - } - if ( $keystore == "ksp" && !$disableCE ) - { - require( 'AE_Ksp.inc' ); - $ksp_path = getKSPPath(); - $dsn .= "CEKeystoreProvider=$ksp_path;CEKeystoreName=$ksp_name;CEKeystoreEncryptKey=$encrypt_key;"; - } - if ( $keywords ) - { - $dsn .= $keywords; - } - $conn = new PDO( $dsn, $uid, $pwd, $options ); - $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - return $conn; - } - catch( PDOException $e ) - { - var_dump( $e ); - exit; - } - catch(Exception $e) - { - var_dump( $e ); - exit; - } -} - - -/** - * @return string CEK name depending on the connection keywords - */ -function getCekName() -{ - require 'MsSetup.inc'; - $cekName = ''; - switch ( $keystore ) { - case "none": - $cekName = ''; - break; - case "win": - $cekName = 'AEColumnKey'; - break; - case "ksp": - $cekName = 'CustomCEK'; - break; - case "akv": - $cekName = 'AKVColumnKey'; - break; - default: - echo "getCekName: Invalid keystore name.\n"; - } - return $cekName; -} - - -/** - * class for encapsulating column metadata needed for creating a table - */ -class columnMeta { - public $colName; - public $dataType; //a string that includes the size of the type if necessary (e.g., decimal(10,5)) - public $encType; //randomized or deterministic; default is deterministic - public $options; //a string that is null by default (e.g. NOT NULL Identity (1,1) ) - - function __construct( $dataType, $colName = null, $options = null, $encType = "deterministic" ) - { - if ( is_null( $colName )) - { - $this->colName = get_default_colname( $dataType ); - } - else - { - $this->colName = $colName; - } - $this->dataType = $dataType; - $this->encType = $encType; - $this->options = $options; - } - /** - * @return string column definition for creating a table - */ - function getColDef() - { - require 'MsSetup.inc'; - $append = " "; - - // an identity column is not encrypted because a select query with identity column as the where clause is often run and the user want to have to bind parameter every time - if ( $keystore != "none" && stripos( $this->options, "identity" ) === false ) - { - $cekName = getCekName(); - if ( stripos( $this->dataType, "char" ) !== false ) - $append .= "COLLATE Latin1_General_BIN2 "; - $append .= sprintf( "ENCRYPTED WITH (ENCRYPTION_TYPE = %s, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = $cekName) ", $this->encType ); - } - $append .= $this->options; - $colDef = "[" . $this->colName . "] " . $this->dataType . $append; - return $colDef; - } -} - - -/** - * @return string default column name when a name is not provided in the columnMeta class - */ -function get_default_colname( $dataType ) -{ - $colName = "c_" . str_replace( ",", "_", str_replace( "(", "_", $dataType )); - $colName = rtrim( $colName, ")" ); - return $colName; -} - - /** * Create a table * @param object $conn : PDO connection object diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index dd183c32a..cf2ff605c 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -13,10 +13,6 @@ // to be runnable from the MSSQL teams' internal proprietary test running system // -const KSP_NAME = 'MyCustomKSPName'; -const ENCRYPT_KEY = 'LPKCWVD07N3RG98J0MBLG4H2'; -const KSP_TEST_TABLE = 'CustomKSPTestTable'; - function isAEQualified($conn) { $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; @@ -52,7 +48,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases require("MsSetup.inc"); - $dsn = getDSN($server, $databaseName, $keywords, $disableCE); + $dsn = getDSN($server, $databaseName, $driver, $keywords, $disableCE); $conn = new PDO($dsn, $uid, $pwd, $options); if ($errmode == PDO::ERRMODE_EXCEPTION || $errmode == PDO::ERRMODE_WARNING || $errmode == PDO::ERRMODE_SILENT) { $conn->setAttribute(PDO::ATTR_ERRMODE, $errmode); @@ -76,7 +72,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP * @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none * @return string dsn string used for PDO constructor */ -function getDSN($sqlsrvserver, $database, $keywords = '', $disableCE = false) +function getDSN($sqlsrvserver, $database, $driver = null, $keywords = '', $disableCE = false) { require("MsSetup.inc"); $dsn = ""; @@ -89,6 +85,9 @@ function getDSN($sqlsrvserver, $database, $keywords = '', $disableCE = false) if ($database) { $dsn .= "database=$database;"; } + if (!is_null($driver)) { + $dsn .= "driver=$driver;"; + } if ($keystore != "none" && !$disableCE) { $dsn .= "ColumnEncryption=Enabled;"; } diff --git a/test/functional/pdo_sqlsrv/MsSetup.inc b/test/functional/pdo_sqlsrv/MsSetup.inc index e07c46a7d..823f283cc 100644 --- a/test/functional/pdo_sqlsrv/MsSetup.inc +++ b/test/functional/pdo_sqlsrv/MsSetup.inc @@ -18,7 +18,6 @@ if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['M $uid = 'TARGET_USERNAME'; $pwd = 'TARGET_PASSWORD'; $databaseName = 'TARGET_DATABASE'; - $DriverName = "ODBC Driver 11 for SQL Server"; } $adServer = 'TARGET_AD_SERVER'; @@ -27,13 +26,12 @@ $adUser = 'TARGET_AD_USERNAME'; $adPassword = 'TARGET_AD_PASSWORD'; $driverType = true; -$PhpDriver = "ODBC Driver 11 for SQL Server"; +$driver = "ODBC Driver 17 for SQL Server"; $tableName = 'pdo_test_table'; $tableIndex = 'php_test_table_idx'; $procName = 'php_test_proc'; $fileName = 'php_test_file.dat'; -$dsn = "odbc:Driver={$DriverName};Server=$server"; $connectionOptions = array(); $daasMode = false; $marsMode = true; diff --git a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt index b5019290d..be6234f2e 100644 --- a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt +++ b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt @@ -1,76 +1,77 @@ ---TEST-- -PDO - Large Column Name Test ---DESCRIPTION-- -Verifies that long column names are supported (up to 128 chars). ---ENV-- -PHPT_EXEC=true ---SKIPIF-- - ---FILE-- +--TEST-- +PDO - Large Column Name Test +--DESCRIPTION-- +Verifies that long column names are supported (up to 128 chars). +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- query("CREATE TABLE [$tableName] ([$columnName] int)"); - $conn->query("INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); $stmt = $conn->query("SELECT * from [$tableName]"); - if ( null == $stmt ) - { - if (!$expectfail) - FatalError("Possible regression: Unable to retrieve inserted value."); - } - - DropTable($conn, $tableName); - + dropTable($conn, $tableName); } //-------------------------------------------------------------------- -// Repro +// repro // //-------------------------------------------------------------------- -function Repro() +function repro() { - $testName = "PDO - Large Column Name Test"; - StartTest($testName); - - $columnName = "a"; - - try - { - for ($a = 1; $a <= 128; $a++) - { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "A"; - } - } - catch (Exception $e) - { - echo $e->getMessage(); + startTest($testName); + + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('A', $maxlen); + + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + echo "Possible regression: Unable to retrieve inserted value\n"; + print_r($e->getMessage()); + echo "\n"; } - - EndTest($testName); + + // Now add another character to the name + $columnName .= 'A'; + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + // Expects to fail + $expected = 'is too long. Maximum length is 128.'; + if (strpos($e->getMessage(), $expected) === false) { + print_r($e->getMessage()); + echo "\n"; + } + } + + endTest($testName); } -Repro(); -?> ---EXPECT-- -Test "PDO - Large Column Name Test" completed successfully. +repro(); +?> +--EXPECT-- +Test "PDO - Large Column Name Test" completed successfully. diff --git a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt index 634dc5272..9ad27164e 100644 --- a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt +++ b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt @@ -8,69 +8,70 @@ PHPT_EXEC=true --FILE-- query("CREATE TABLE [$tableName] ([$columnName] int)"); - $conn->query("INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); $stmt = $conn->query("SELECT * from [$tableName]"); - if ( null == $stmt ) - { - if (!$expectfail) - FatalError("Possible regression: Unable to retrieve inserted value."); - } - - DropTable($conn, $tableName); - + dropTable($conn, $tableName); } //-------------------------------------------------------------------- -// Repro +// repro // //-------------------------------------------------------------------- -function Repro() +function repro() { - $testName = "PDO - Large Column Name Test"; - StartTest($testName); + startTest($testName); - $columnName = "是"; + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('是', $maxlen); - try - { - for ($a = 1; $a <= 128; $a++) - { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "是"; - } + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + echo "Possible regression: Unable to retrieve inserted value\n"; + print_r($e->getMessage()); + echo "\n"; } - catch (Exception $e) - { - echo $e->getMessage(); + + // Now add another character to the name + $columnName .= '是'; + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + // Expects to fail + $expected = 'is too long. Maximum length is 128.'; + if (strpos($e->getMessage(), $expected) === false) { + print_r($e->getMessage()); + echo "\n"; + } } - EndTest($testName); + endTest($testName); } -Repro(); +repro(); + ?> --EXPECT-- Test "PDO - Large Column Name Test" completed successfully. diff --git a/test/functional/pdo_sqlsrv/PDO21_Connection.phpt b/test/functional/pdo_sqlsrv/PDO21_Connection.phpt index f09899626..9498a3585 100644 --- a/test/functional/pdo_sqlsrv/PDO21_Connection.phpt +++ b/test/functional/pdo_sqlsrv/PDO21_Connection.phpt @@ -15,7 +15,7 @@ try { // Invalid connection attempt => errors are expected $serverName="InvalidServerName"; - $dsn = getDSN($serverName, $databaseName); + $dsn = getDSN($serverName, $databaseName, $driver); $conn1 = new PDO($dsn, $uid, $pwd, $connectionOptions); if ($conn1) { printf("Invalid connection attempt should have failed.\n"); diff --git a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt index 05bf24a71..c68a46b14 100644 --- a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt +++ b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt @@ -6,6 +6,15 @@ This test assumes the default odbcinst.ini has not been modified. --FILE-- exec($sql); diff --git a/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt b/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt index 93b6e2fd4..c8424856d 100644 --- a/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt +++ b/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt @@ -12,6 +12,8 @@ try { // Create table $tableName = 'pdo_040test'; + dropTable($conn, $tableName); + // common function insertRow() is not used here since the test deliberately // executes an invalid insertion statement // thus it's not necessary to create an encrypted column for testing column encryption diff --git a/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt b/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt index 883da0299..8f2d8e8f7 100644 --- a/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt +++ b/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt @@ -13,7 +13,7 @@ require_once("MsCommon_mid-refactor.inc"); try { echo "Testing a connection with ATTR_PERSISTENT...\n"; // setting PDO::ATTR_PERSISTENT in PDO constructor returns an exception - $dsn = getDSN($server, $databaseName); + $dsn = getDSN($server, $databaseName, $driver); $attr = array(PDO::ATTR_PERSISTENT => true); $conn = new PDO($dsn, $uid, $pwd, $attr); //free the connection diff --git a/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt b/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt index 463b7ac4d..88f47495f 100644 --- a/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt +++ b/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt @@ -10,7 +10,7 @@ require_once("MsSetup.inc"); require_once("MsCommon_mid-refactor.inc"); try { echo "Testing a connection with ATTR_PREFETCH before ERRMODE_EXCEPTION...\n"; - $dsn = getDSN($server, $databaseName); + $dsn = getDSN($server, $databaseName, $driver); $attr = array(PDO::ATTR_PREFETCH => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); $conn = new PDO($dsn, $uid, $pwd, $attr); diff --git a/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt b/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt index 6b59c134e..67c0f4fd0 100644 --- a/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt @@ -7,7 +7,7 @@ do not need to be encrypted --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- "int", createTable($conn, $tbname, $colMetaArr); // Create a Store Procedure $spname = 'selectAllColumns'; +dropProc($conn, $spname); $spSql = "CREATE PROCEDURE $spname ( @c1_int int OUTPUT, @c2_smallint smallint OUTPUT, @c3_tinyint tinyint OUTPUT, @c4_bit bit OUTPUT, diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt index 8b3b0a0e6..124df22b7 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt @@ -1,7 +1,7 @@ --TEST-- Test new connection keyword ColumnEncryption --SKIPIF-- - + --FILE-- getMessage()); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S02]')===false or (strpos($err, 'TCP Provider')===false and strpos($err, 'SMux Provider')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } unset($conn); @@ -200,14 +204,18 @@ try { } } catch (PDOException $e) { echo "Error executing statement 8.\n"; - print_r($e->getMessage()); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[IMSSP]')===false or strpos($err, 'The connection cannot process this operation because there is a statement with pending results')===false) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } unset($conn); unset($conn_break); ?> ---EXPECTREGEX-- +--EXPECT-- Statement 1 successful. 16 rows in result set. Statement 2 successful. @@ -219,7 +227,5 @@ Statement 4 successful. Statement 5 successful. -1 rows in result set. Error executing statement 6. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. Statement 7 successful. Error executing statement 8. -SQLSTATE\[IMSSP\]: The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt index 1d7b066e9..b012eed98 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt @@ -1,8 +1,7 @@ --TEST-- Test the connection resiliency keywords ConnectRetryCount and ConnectRetryInterval and their ranges of acceptable values --SKIPIF-- - + --FILE-- getMessage() ); + echo "Transaction failed.\n"; + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S02]')===false or (strpos($err, 'TCP Provider')===false and strpos($err, 'SMux Provider')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } // This try catch block prevents an Uncaught PDOException error that occurs @@ -201,17 +206,20 @@ try } catch ( PDOException $e ) { - print_r( $e->getMessage() ); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S01]')===false or strpos($err, 'Communication link failure')===false) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } $conn_break = null; ?> ---EXPECTREGEX-- +--EXPECT-- Statement 1 prepared. Statement 1 executed. Transaction begun. Transaction was committed. Transaction begun. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. -SQLSTATE\[08S01\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure +Transaction failed. diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt index 4389b3a95..279a7eb48 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt @@ -40,7 +40,11 @@ try catch( PDOException $e ) { echo "Error executing statement 1.\n"; - print_r( $e->getMessage() ); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S02]')===false or (strpos($err, 'TCP Provider')===false and strpos($err, 'SMux Provider')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } $conn = null; @@ -82,7 +86,6 @@ $conn_break = null; DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); ?> ---EXPECTREGEX-- +--EXPECT-- Error executing statement 1. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. Query successfully executed. diff --git a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt index f51d3ba6b..a5c31e09e 100644 --- a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt +++ b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt @@ -4,6 +4,7 @@ UTF-8 connection strings --FILE-- query( "SELECT @@VERSION" ); -if ($stmt) { - $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; -} else { - die( "skip Could not fetch SQL Server version during SKIPIF."); +$msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if (!$is_win) { + if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { + die("skip Unsupported ODBC driver version"); + } } -$version = explode(' ', $ver_string); +// Get SQL Server Version +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = $conn->query( "SELECT @@VERSION" ); + if ($stmt) { + $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; + } else { + die( "skip Could not fetch SQL Server version during SKIPIF."); + } + + $version = explode(' ', $ver_string); -if ($version[3] < '2014') { - die("skip Wrong version of SQL Server, 2014 or later required"); + if ($version[3] < '2014') { + die("skip Wrong version of SQL Server, 2014 or later required"); + } } ?> diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc index e2f9456bf..72553974b 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc @@ -1,10 +1,14 @@ query( "SELECT @@VERSION" ); -if ($stmt) { - $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; -} else { - die( "skip Could not fetch SQL Server version during SKIPIF."); +$msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if (!$is_win) { + if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); + } } -$version = explode(' ', $ver_string); +// Get SQL Server Version +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = $conn->query( "SELECT @@VERSION" ); + if ($stmt) { + $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; + } else { + die( "skip Could not fetch SQL Server version during SKIPIF."); + } + + $version = explode(' ', $ver_string); -if ($version[3] < '2016') { - die("skip Wrong version of SQL Server, 2016 or later required"); + if ($version[3] < '2016') { + die("skip Wrong version of SQL Server, 2016 or later required"); + } } ?> diff --git a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt index ed42f74c6..e8f46546d 100644 --- a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt @@ -1,36 +1,49 @@ ---TEST-- -retrieval of names of column master key and column encryption key generated in the database setup ---SKIPIF-- - ---FILE-- -query($query); - $master_key_row = $stmt->fetch(); - - $query = "SELECT name FROM sys.column_encryption_keys"; - $stmt = $conn->query($query); - $encryption_key_row = $stmt->fetch(); - - if ($master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){ - echo "Test Successfully done.\n"; - } - else { - die("Column Master Key and Column Encryption Key not created.\n"); - } - unset($stmt); -} -else { - echo "Test Successfully done.\n"; -} -unset($conn); -?> ---EXPECT-- +--TEST-- +Test the existence of Windows Always Encrypted keys generated in the database setup +--DESCRIPTION-- +This test iterates through the rows of sys.column_master_keys and/or +sys.column_encryption_keys to look for the specific column master key and +column encryption key generated in the database setup +--SKIPIF-- + +--FILE-- +query($query); + + // Do not assume the master key must be the first one created + $found = false; + while ($master_key_row = $stmt->fetch()) { + if ($master_key_row[0] == 'AEMasterKey') { + $found = true; + } + } + if (!$found) { + die("Windows Column Master Key not created.\n"); + } + + // Do not assume the encryption key must be the first one created + $query = "SELECT name FROM sys.column_encryption_keys"; + $stmt = $conn->query($query); + + $found = false; + while ($encryption_key_row = $stmt->fetch()) { + if ($encryption_key_row[0] == 'AEColumnKey') { + $found = true; + } + } + if (!$found) { + die("Windows Column Encryption Key not created.\n"); + } + unset($stmt); +} + +echo "Test Successfully done.\n"; +unset($conn); +?> +--EXPECT-- Test Successfully done. \ No newline at end of file diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index 13900e2ec..def2d0f50 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -472,7 +472,7 @@ function isLocaleSupported() if (isWindows()) { return true; } - if (AE\isColEncrypted()) { + if (AE\isDataEncrypted()) { return false; } // now check ODBC version diff --git a/test/functional/sqlsrv/MsSetup.inc b/test/functional/sqlsrv/MsSetup.inc index 1ec7ceef4..aec2b4bb1 100644 --- a/test/functional/sqlsrv/MsSetup.inc +++ b/test/functional/sqlsrv/MsSetup.inc @@ -18,7 +18,9 @@ $tableIndex = "php_test_table_index"; $procName = "php_test_proc"; $fileName = "php_test_file.dat"; -$connectionOptions = array("Database"=>$database, "UID"=>$userName, "PWD"=>$userPassword, "TraceOn"=>false); +$driver = "ODBC Driver 17 for SQL Server"; + +$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, "TraceOn" => false, "Driver" => $driver); $daasMode = false; $marsMode = true; diff --git a/test/functional/sqlsrv/TC52_StreamSend.phpt b/test/functional/sqlsrv/TC52_StreamSend.phpt index 648c23fdf..3779e4448 100644 --- a/test/functional/sqlsrv/TC52_StreamSend.phpt +++ b/test/functional/sqlsrv/TC52_StreamSend.phpt @@ -19,7 +19,7 @@ function sendStream($minType, $maxType, $atExec) startTest($testName); setup(); - $tableName = "TC52test"; + $tableName = "TC52test" . rand(0, 100); $fileName = "TC52test.dat"; $conn1 = AE\connect(); diff --git a/test/functional/sqlsrv/TC54_StreamPrepared.phpt b/test/functional/sqlsrv/TC54_StreamPrepared.phpt index 94a6bbe88..a22868f90 100644 --- a/test/functional/sqlsrv/TC54_StreamPrepared.phpt +++ b/test/functional/sqlsrv/TC54_StreamPrepared.phpt @@ -18,7 +18,7 @@ function sendStream($minType, $maxType) startTest($testName); setup(); - $tableName = "TC54test"; + $tableName = "TC54test" . rand(0, 100); $fileName = "TC53test.dat"; $conn1 = AE\connect(); diff --git a/test/functional/sqlsrv/TC84_LargeColumnName.phpt b/test/functional/sqlsrv/TC84_LargeColumnName.phpt index bebcfadc4..b7cc3d570 100644 --- a/test/functional/sqlsrv/TC84_LargeColumnName.phpt +++ b/test/functional/sqlsrv/TC84_LargeColumnName.phpt @@ -10,7 +10,7 @@ PHPT_EXEC=true 128); - $columnName .= "A"; - } - } catch (Exception $e) { - echo $e->getMessage(); - } + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('a', $maxlen); + largeColumnNameTest($columnName); + + // Now add another character to the name + $columnName .= "A"; + + largeColumnNameTest($columnName, true); endTest($testName); } diff --git a/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt b/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt index 48bdc11c9..7a0aca36f 100644 --- a/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt +++ b/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt @@ -10,35 +10,44 @@ PHPT_EXEC=true 'UTF-8' )); + $conn = connect(array('CharacterSet'=>'UTF-8')); $tableName = "LargeColumnNameTest"; dropTable($conn, $tableName); - sqlsrv_query($conn, "CREATE TABLE [$tableName] ([$columnName] int)"); - - sqlsrv_query($conn, "INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); + $stmt = sqlsrv_query($conn, "CREATE TABLE [$tableName] ([$columnName] int)"); + if ($stmt == null) { + if (!$expectFail) { + fatalError("Possible regression: Unable to create test $tableName."); + } else { + $expected = 'is too long. Maximum length is 128.'; + if (strpos(sqlsrv_errors()[0]['message'], $expected) === false) { + print_r(sqlsrv_errors()); + } + echo "$"; + echo "stmt = null"; + echo "\n"; + } + } else { + sqlsrv_query($conn, "INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); - $stmt = sqlsrv_query($conn, "SELECT * from [$tableName]"); + $stmt = sqlsrv_query($conn, "SELECT * from [$tableName]"); - if (null == $stmt) { - echo "$"; - echo "stmt = null"; - echo "\n"; - } else { if (null == sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { - if (!$expectfail) { + if (!$expectFail) { fatalError("Possible regression: Unable to retrieve inserted value."); } } sqlsrv_free_stmt($stmt); } + dropTable($conn, $tableName); + sqlsrv_close($conn); } @@ -53,16 +62,16 @@ function repro() startTest($testName); - $columnName = "银"; + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('银', $maxlen); - try { - for ($a = 1; $a <= 129; $a++) { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "银"; - } - } catch (Exception $e) { - echo $e->getMessage(); - } + largeColumnNameTest($columnName); + + // Now add another character to the name + $columnName .= "银"; + + largeColumnNameTest($columnName, true); endTest($testName); } diff --git a/test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt b/test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt deleted file mode 100644 index dc7a1eb2d..000000000 --- a/test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt +++ /dev/null @@ -1,75 +0,0 @@ ---TEST-- -PHP - Large Unicode Column Name Test ---DESCRIPTION-- -Verifies that long column names are supported (up to 128 chars). ---ENV-- -PHPT_EXEC=true ---SKIPIF-- - ---FILE-- -'UTF-8' )); - - $tableName = "LargeColumnNameTest"; - - dropTable($conn, $tableName); - - sqlsrv_query($conn, "CREATE TABLE [$tableName] ([$columnName] int)"); - - sqlsrv_query($conn, "INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); - - $stmt = sqlsrv_query($conn, "SELECT * from [$tableName]"); - - if (null == $stmt) { - echo "$"; - echo "stmt = null"; - echo "\n"; - } else { - if (null == sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { - if (!$expectfail) { - fatalError("Possible regression: Unable to retrieve inserted value."); - } - } - sqlsrv_free_stmt($stmt); - } - - sqlsrv_close($conn); -} - - -//-------------------------------------------------------------------- -// repro -// -//-------------------------------------------------------------------- -function repro() -{ - $testName = "PHP - Large Unicode Column Name Test"; - - startTest($testName); - - $columnName = "银"; - - try { - for ($a = 1; $a <= 129; $a++) { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "银"; - } - } catch (Exception $e) { - echo $e->getMessage(); - } - - - endTest($testName); -} - -repro(); -?> ---EXPECT-- -$stmt = null -Test "PHP - Large Unicode Column Name Test" completed successfully. diff --git a/test/functional/sqlsrv/connection_resiliency.phpt b/test/functional/sqlsrv/connection_resiliency.phpt index 59ee6c277..2d47f6fe5 100644 --- a/test/functional/sqlsrv/connection_resiliency.phpt +++ b/test/functional/sqlsrv/connection_resiliency.phpt @@ -144,7 +144,12 @@ $stmt6 = sqlsrv_query( $conn, "SELECT * FROM $tableName2" ); if( $stmt6 === false ) { echo "Error in statement 6.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], '08S01')===false or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } else { @@ -188,7 +193,12 @@ $stmt8 = sqlsrv_query( $conn, "SELECT * FROM $tableName2" ); if( $stmt8 === false ) { echo "Error in statement 8.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], 'IMSSP')===false or + strpos($err[0][2], 'The connection cannot process this operation because there is a statement with pending results')===false) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } else { @@ -199,7 +209,7 @@ sqlsrv_close( $conn ); sqlsrv_close( $conn_break ); ?> ---EXPECTREGEX-- +--EXPECT-- Statement 1 successful. 16 rows in result set. Statement 2 successful. @@ -211,53 +221,5 @@ Statement 4 successful. Statement 5 successful. rows in result set. Error in statement 6. -Array -\( - \[0\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. - - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. - - \) - - \[1\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \) - -\) Statement 7 successful. Error in statement 8. -Array -\( - \[0\] => Array - \( - \[0\] => IMSSP - \[SQLSTATE\] => IMSSP - \[1\] => -44 - \[code\] => -44 - \[2\] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. - \[message\] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. - \) - - \[1\] => Array - \( - \[0\] => HY000 - \[SQLSTATE\] => HY000 - \[1\] => 0 - \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command - \) - -\) diff --git a/test/functional/sqlsrv/connection_resiliency_keywords.phpt b/test/functional/sqlsrv/connection_resiliency_keywords.phpt index 0211b47c1..7dd50fb57 100644 --- a/test/functional/sqlsrv/connection_resiliency_keywords.phpt +++ b/test/functional/sqlsrv/connection_resiliency_keywords.phpt @@ -3,8 +3,7 @@ Test the connection resiliency keywords --DESCRIPTION-- Test the connection resiliency keywords ConnectRetryCount and ConnectRetryInterval and their ranges of acceptable values --SKIPIF-- - + --FILE-- 08001 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[2\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[message\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' \) \) @@ -90,8 +89,8 @@ Array \[SQLSTATE\] => 08001 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' + \[2\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' + \[message\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' \) \) @@ -104,8 +103,8 @@ Array \[SQLSTATE\] => 08001 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[2\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[message\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' \) \) diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 3d713c2ee..c42a4e447 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -167,42 +167,22 @@ else else { echo "Statement not valid and rollback failed.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], '08S02')===false or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } } sqlsrv_close( $conn ); sqlsrv_close( $conn_break ); ?> ---EXPECTREGEX-- +--EXPECT-- Statement 1 prepared. Statement 1 executed. Transaction begun. Transaction was committed. Transaction begun. Statement not valid and rollback failed. -Array -\( - \[0\] => Array - \( - \[0\] => 08S02 - \[SQLSTATE\] => 08S02 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. - - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. - - \) - - \[1\] => Array - \( - \[0\] => 08S02 - \[SQLSTATE\] => 08S02 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Unable to open a logical session - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Unable to open a logical session - \) - -\) diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index 06a8d28f3..1abfdd309 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -33,7 +33,12 @@ $stmt1 = sqlsrv_query( $conn, "SELECT * FROM $tableName1" ); if( $stmt1 === false ) { echo "Error in statement 1.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], '08S01')===false or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } else { @@ -78,29 +83,4 @@ DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ) ?> --EXPECTREGEX-- Error in statement 1. -Array -\( - \[0\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. - - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. - - \) - - \[1\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \) - -\) Statement 2 successful. diff --git a/test/functional/sqlsrv/skipif_not_akv.inc b/test/functional/sqlsrv/skipif_not_akv.inc new file mode 100644 index 000000000..9746db712 --- /dev/null +++ b/test/functional/sqlsrv/skipif_not_akv.inc @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_protocol_not_tcp.inc b/test/functional/sqlsrv/skipif_protocol_not_tcp.inc index 4dc4921e7..26d960ca3 100644 --- a/test/functional/sqlsrv/skipif_protocol_not_tcp.inc +++ b/test/functional/sqlsrv/skipif_protocol_not_tcp.inc @@ -1,6 +1,4 @@ $userName, "PWD"=>$userPassword ); @@ -14,17 +18,30 @@ if ($conn === false) { die( "skip Could not connect during SKIPIF." ); } -// Get SQL Server version -$stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); -if (sqlsrv_fetch($stmt)) { - $ver_string = sqlsrv_get_field( $stmt, 0 ); -} else { - die("skip Could not fetch SQL Server version."); +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if (!$is_win) { + if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { + die("skip Unsupported ODBC driver version"); + } } -$version = explode(' ', $ver_string); +// Get SQL Server version +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); + if (sqlsrv_fetch($stmt)) { + $ver_string = sqlsrv_get_field( $stmt, 0 ); + } else { + die("skip Could not fetch SQL Server version."); + } + + $version = explode(' ', $ver_string); -if ($version[3] < '2014') { - die("skip Wrong version of SQL Server, 2014 or later required"); + if ($version[3] < '2014') { + die("skip Wrong version of SQL Server, 2014 or later required"); + } } ?> diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc index 692523d66..ce06258a2 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc @@ -1,10 +1,14 @@ $userName, "PWD"=>$userPassword ); @@ -14,17 +18,29 @@ if ($conn === false) { die( "skip Could not connect during SKIPIF." ); } -// Get SQL Server version -$stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); -if (sqlsrv_fetch($stmt)) { - $ver_string = sqlsrv_get_field( $stmt, 0 ); -} else { - die("skip Could not fetch SQL Server version."); +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; + +if (!$is_win) { + if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); + } } -$version = explode(' ', $ver_string); +// Get SQL Server version +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); + if (sqlsrv_fetch($stmt)) { + $ver_string = sqlsrv_get_field( $stmt, 0 ); + } else { + die("skip Could not fetch SQL Server version."); + } + + $version = explode(' ', $ver_string); -if ($version[3] < '2016') { - die("skip Wrong version of SQL Server, 2016 or later required"); + if ($version[3] < '2016') { + die("skip Wrong version of SQL Server, 2016 or later required"); + } } ?> diff --git a/test/functional/sqlsrv/sqlsrv_ConnPool_Unix.phpt b/test/functional/sqlsrv/sqlsrv_ConnPool_Unix.phpt index 57aafff60..bd00ac689 100644 --- a/test/functional/sqlsrv/sqlsrv_ConnPool_Unix.phpt +++ b/test/functional/sqlsrv/sqlsrv_ConnPool_Unix.phpt @@ -6,6 +6,15 @@ This test assumes the default odbcinst.ini has not been modified. --FILE-- + --FILE-- + --FILE-- + --FILE-- --FILE-- s; - - if ($diff == 0) { - $value = $valueAE; - } + + $dataArray = sqlsrv_fetch_array($stmt2, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < sizeof($SQLSRV_PHPTYPE_CONST); ++$i) { + if (!sqlsrv_execute($stmt)) { + fatalError("Execute failed for $SQLSRV_PHPTYPE_CONST[$i]\n"); + } + + if ($result = sqlsrv_fetch($stmt)) { + for ($j = 0; $j < $numFields; $j++) { + $value = sqlsrv_get_field($stmt, $j, $SQLSRV_PHPTYPE_CONST[$i]); + $valueFromArray = $dataArray[$j]; + + // PHPTYPE_STREAM returns a PHP resource, so check the type + if (is_resource($value)) { + $value = get_resource_type($value); } - - if ($valueAE != $value or $valueFromArrayAE != $valueFromArray) { - echo "Values do not match! PHPType $i Field $j\n"; - print_r($valueAE);echo "\n"; - print_r($value);echo "\n"; - print_r($valueFromArrayAE);echo "\n"; - print_r($valueFromArray);echo "\n"; - print_r(sqlsrv_errors()); - fatalError("Test failed, values do not match.\n"); + + // For each type, the AE values come first and non-AE values second + // So let's do the comparison every second field + if ($j%2 == 0) { + $valueAE = $value; + $valueFromArrayAE = $valueFromArray; + } elseif ($j%2 == 1) { + // If returning a DateTime PHP type from a date only SQL type, + // PHP adds the current timestamp to make a DateTime object, + // and in this case the AE and non-AE times may be off by a + // fraction of a second since they are retrieved at ever-so-slightly + // different times. This not a test-failing discrepancy, so + // below the DateTime objects are made equal again for the next if + // block. + if ($value instanceof DateTime) { + // date_diff returns a DateInterval object, and s is + // the difference in seconds. s should be zero because + // the difference should be just a fraction of a second. + $datediff = date_diff($value, $valueAE); + $diff = $datediff->s; + + if ($diff == 0) { + $value = $valueAE; + } + } + + if ($valueAE != $value or $valueFromArrayAE != $valueFromArray) { + $index = floor($j / 2); + echo "Values do not match! PHPType $i Field $dataTypes[$index]\n"; + print_r($valueAE); + echo "\n--------\n\n"; + print_r($value); + echo "\n--------\n\n"; + print_r($valueFromArrayAE); + echo "\n--------\n\n"; + print_r($valueFromArray); + echo "\n--------\n\n"; + print_r(sqlsrv_errors()); + echo("Test failed, values do not match.\n"); + } } } } - ++$i; } - + sqlsrv_free_stmt($stmt); sqlsrv_free_stmt($stmt2); - - $deleteQuery = "DELETE FROM $tableName"; + + $deleteQuery = "TRUNCATE TABLE $tableName"; $stmt = sqlsrv_query($conn, $deleteQuery); if ($stmt == false) { print_r(sqlsrv_errors()); - fatalError("Delete statement failed"); + fatalError("Truncate statement failed"); } - + sqlsrv_free_stmt($stmt); } diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt index 5a39140e1..e6a5bbb6d 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt @@ -1,7 +1,7 @@ --TEST-- Test new connection keyword ColumnEncryption --SKIPIF-- - + --FILE-- --FILE-- @@ -13,23 +17,34 @@ $conn = connect(); if (AE\IsQualified($conn)) { $query = "SELECT name FROM sys.column_master_keys"; $stmt = sqlsrv_query($conn, $query); - sqlsrv_fetch($stmt); - $master_key_name = sqlsrv_get_field($stmt, 0); + $found = false; + while (sqlsrv_fetch($stmt)) { + $master_key_name = sqlsrv_get_field($stmt, 0); + if ($master_key_name == 'AEMasterKey') { + $found = true; + } + } + // $master_key_name = sqlsrv_get_field($stmt, 0); + if (!$found) { + die("Windows Column Master Key not created.\n"); + } $query = "SELECT name FROM sys.column_encryption_keys"; $stmt = sqlsrv_query($conn, $query); - sqlsrv_fetch($stmt); - $encryption_key_name = sqlsrv_get_field($stmt, 0); - - if ($master_key_name == 'AEMasterKey' && $encryption_key_name == 'AEColumnKey') { - echo "Test Successfully done.\n"; - } else { - echo "Column Master Key and Column Encryption Key not created.\n"; + $found = false; + while (sqlsrv_fetch($stmt)) { + $encryption_key_name = sqlsrv_get_field($stmt, 0); + if ($encryption_key_name == 'AEColumnKey') { + $found = true; + } + } + if (!$found) { + die("Windows Column Encryption Key not created.\n"); } sqlsrv_free_stmt($stmt); -} else { - echo "Test Successfully done.\n"; } + +echo "Test Successfully done.\n"; sqlsrv_close($conn); ?> --EXPECT-- From eb7cb710aa9547620b5900d9adf1fc09bb5f9d62 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 9 Oct 2018 15:53:10 -0700 Subject: [PATCH 2/7] Feature request 415 --- source/shared/core_sqlsrv.h | 79 +++-- source/shared/core_stmt.cpp | 159 +++++++++- source/sqlsrv/conn.cpp | 7 + source/sqlsrv/util.cpp | 8 + .../sqlsrv_statement_format_decimals.phpt | 297 ++++++++++++++++++ ...lsrv_statement_format_decimals_scales.phpt | 255 +++++++++++++++ 6 files changed, 778 insertions(+), 27 deletions(-) create mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index b623c1b4a..35e49cbe3 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1107,6 +1107,7 @@ enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_SCROLLABLE, SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, SQLSRV_STMT_OPTION_DATE_AS_STRING, + SQLSRV_STMT_OPTION_FORMAT_DECIMALS, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, @@ -1296,6 +1297,11 @@ struct stmt_option_date_as_string : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_format_decimals : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); +}; + // used to hold the table for statment options struct stmt_option { @@ -1334,16 +1340,39 @@ extern php_stream_wrapper g_sqlsrv_stream_wrapper; #define SQLSRV_STREAM_WRAPPER "sqlsrv" #define SQLSRV_STREAM "sqlsrv_stream" +// *** parameter metadata struct *** +struct param_meta_data +{ + SQLSMALLINT sql_type; + SQLSMALLINT decimal_digits; + SQLSMALLINT nullable; + SQLULEN column_size; + + param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0) + { + } + + ~param_meta_data() + { + } + + SQLSMALLINT get_sql_type() { return sql_type; } + SQLSMALLINT get_decimal_digits() { return decimal_digits; } + SQLSMALLINT get_nullable() { return nullable; } + SQLULEN get_column_size() { return column_size; } +}; + // holds the output parameter information. Strings also need the encoding and other information for // after processing. Only integer, float, and strings are allowable output parameters. struct sqlsrv_output_param { zval* param_z; SQLSRV_ENCODING encoding; - SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement - SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer - SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement + SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer + SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary bool is_bool; + param_meta_data meta_data; // parameter meta data // string output param constructor sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) : @@ -1361,34 +1390,31 @@ struct sqlsrv_output_param { php_out_type(php_out_type) { } -}; -// forward decls -struct sqlsrv_result_set; -struct field_meta_data; - -// *** parameter metadata struct *** -struct param_meta_data -{ - SQLSMALLINT sql_type; - SQLSMALLINT decimal_digits; - SQLSMALLINT nullable; - SQLULEN column_size; - - param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0) - { + void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE) + { + meta_data.sql_type = sql_type; + meta_data.column_size = column_size; + meta_data.decimal_digits = decimal_digits; + meta_data.nullable = nullable; } - ~param_meta_data() - { + SQLSMALLINT getDecimalDigits() + { + // Return decimal_digits only for decimal / numeric types. Otherwise, return -1 + if (meta_data.sql_type == SQL_DECIMAL || meta_data.sql_type == SQL_NUMERIC) { + return meta_data.decimal_digits; + } + else { + return -1; + } } - - SQLSMALLINT get_sql_type() { return sql_type; } - SQLSMALLINT get_decimal_digits() { return decimal_digits; } - SQLSMALLINT get_nullable() { return nullable; } - SQLULEN get_column_size() { return column_size; } }; +// forward decls +struct sqlsrv_result_set; +struct field_meta_data; + // *** Statement resource structure *** struct sqlsrv_stmt : public sqlsrv_context { @@ -1409,6 +1435,7 @@ struct sqlsrv_stmt : public sqlsrv_context { unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings + short num_decimals; // indicates number of decimals shown in fetched results (-1 by default, which means no formatting required) // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving @@ -1743,6 +1770,8 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, + SQLSRV_ERROR_INVALID_FORMAT_DECIMALS, + SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, // 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 ddb1b981a..84ed3875c 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -107,6 +107,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); +void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ); @@ -141,8 +142,9 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error past_next_result_end( false ), query_timeout( QUERY_TIMEOUT_INVALID ), date_as_string(false), + num_decimals(-1), // -1 means no formatting required buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), - param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte + param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte send_streams_at_exec( true ), current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), current_stream_read( 0 ) @@ -571,6 +573,8 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ // save the parameter to be adjusted and/or converted after the results are processed sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len ) ); + output_param.saveMetaData(sql_type, column_size, decimal_digits); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); // For output parameters, if we set the column_size to be same as the buffer_len, @@ -1416,6 +1420,21 @@ void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op } } +void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +{ + // first check if the input is an integer + CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) { + throw core::CoreException(); + } + + short format_decimals = static_cast(Z_LVAL_P(value_z)); + CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) { + throw core::CoreException(); + } + + stmt->num_decimals = format_decimals; +} + // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) @@ -2079,6 +2098,133 @@ void field_cache_dtor( _Inout_ zval* data_z ) sqlsrv_free( cache ); } +// To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string() +void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len) +{ + // In SQL Server, the default maximum precision of numeric and decimal data types is 38 + // + // Note: stmt->num_decimals is -1 by default, which means no formatting on decimals / numerics is necessary + // If the required number of decimals is larger than the field scale, will use the column field scale instead. + // This is to ensure the number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. + // + // Note: it's possible that the decimal / numeric value does not contain a decimal dot because the field scale is 0. + // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of decimals_digits + // + std::string str = field_value; + size_t pos = str.find_first_of('.'); + + if (pos == std::string::npos || decimals_digits < 0) { + return; + } + + SQLSMALLINT num_decimals = decimals_digits; + if (num_decimals > field_scale) { + num_decimals = field_scale; + } + + // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php + // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is + // followed by 5 or above. + + bool isNegative = false; + + // If negative, remove the minus sign for now so as not to complicate the rounding process + if (str[0] == '-') { + isNegative = true; + std::ostringstream oss; + oss << str.substr(1); + str = oss.str(); + pos = str.find_first_of('.'); + } + + // Adds the leading zero if not exists + if (pos == 0) { + std::ostringstream oss; + oss << '0' << str; + str = oss.str(); + pos++; + } + if (num_decimals == 0) { + // Chop all decimal digits, including the decimal dot + size_t pos2 = pos + 1; + short n = str[pos2] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of the dot all the way to the first digit + bool carry_over = true; + for (short p = pos - 1; p >= 0 && carry_over; p--) { + n = str[p] - '0'; + if (n == 9) { + str[p] = '0' ; + carry_over = true; + } + else { + n++; + carry_over = false; + str[p] = '0' + n; + } + } + if (carry_over) { + std::ostringstream oss; + oss << '1' << str.substr(0, pos); + str = oss.str(); + pos++; + } + } + + if (isNegative) { + std::ostringstream oss; + oss << '-' << str.substr(0, pos); + str = oss.str(); + } else { + str = str.substr(0, pos); + } + } + else { + size_t pos2 = pos + num_decimals + 1; + // No need to check if rounding is necessary when pos2 has passed the last digit in the input string + if (pos2 < str.length()) { + short n = str[pos2] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of pos2 all the way to the first digit + bool carry_over = true; + for (short p = pos2 - 1; p >= 0 && carry_over; p--) { + if (str[p] == '.') { // Skip the dot + continue; + } + n = str[p] - '0'; + if (n == 9) { + str[p] = '0' ; + carry_over = true; + } + else { + n++; + carry_over = false; + str[p] = '0' + n; + } + } + if (carry_over) { + std::ostringstream oss; + oss << '1' << str.substr(0, pos2); + str = oss.str(); + pos2++; + } + } + } + + if (isNegative) { + std::ostringstream oss; + oss << '-' << str.substr(0, pos2); + str = oss.str(); + } else { + str = str.substr(0, pos2); + } + } + + size_t len = str.length(); + str.copy(field_value, len); + field_value[len] = '\0'; + *field_len = len; +} // To be called after all results are processed. ODBC and SQL Server do not guarantee that all output // parameters will be present until all results are processed (since output parameters can depend on results @@ -2160,6 +2306,11 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) core::sqlsrv_zval_stringl(value_z, str, str_len); } else { + SQLSMALLINT decimal_digits = output_param->getDecimalDigits(); + if (stmt->num_decimals >= 0 && decimal_digits >= 0) { + format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len); + } + core::sqlsrv_zval_stringl(value_z, str, str_len); } } @@ -2214,7 +2365,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind { SQLRETURN r; SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; + SQLSMALLINT sql_field_type = 0; SQLSMALLINT extra = 0; SQLLEN field_len_temp = 0; SQLLEN sql_display_size = 0; @@ -2425,6 +2576,10 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind throw core::CoreException(); } } + + if (stmt->num_decimals >= 0 && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) { + format_decimal_numbers(stmt->num_decimals, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp); + } } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) else { diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index dcfc755e2..d5f324a23 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -174,6 +174,7 @@ namespace SSStmtOptionNames { const char SCROLLABLE[] = "Scrollable"; const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; const char DATE_AS_STRING[] = "ReturnDatesAsStrings"; + const char FORMAT_DECIMALS[] = "FormatDecimals"; } namespace SSConnOptionNames { @@ -250,6 +251,12 @@ const stmt_option SS_STMT_OPTS[] = { SQLSRV_STMT_OPTION_DATE_AS_STRING, std::unique_ptr( new stmt_option_date_as_string ) }, + { + SSStmtOptionNames::FORMAT_DECIMALS, + sizeof( SSStmtOptionNames::FORMAT_DECIMALS ), + SQLSRV_STMT_OPTION_FORMAT_DECIMALS, + std::unique_ptr( new stmt_option_format_decimals ) + }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index aab5e2ebd..545f699d0 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -428,6 +428,14 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false} }, + { + SQLSRV_ERROR_INVALID_FORMAT_DECIMALS, + { IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -117, false} + }, + { + SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, + { IMSSP, (SQLCHAR*) "For formatting decimal data values, %1!d! is out of range. Expected an integer from 0 to 38, inclusive.", -118, true} + }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt new file mode 100644 index 000000000..9b48d207e --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -0,0 +1,297 @@ +--TEST-- +Test how decimal data output values can be formatted (feature request issue 415) +--DESCRIPTION-- +Test how numeric and decimal data output values can be formatted by using the +statement option FormatDecimals, which expects an integer value from the range [0,38], +affecting only the money / decimal types in the fetched result set because they are +always strings to preserve accuracy and precision, unlike other primitive numeric +types that can be retrieved as numbers. + +No effect on other operations like insertion or update. + +1. By default, data will be returned with the original precision and scale +2. The data column original scale still takes precedence – for example, if the user +specifies 3 decimal digits for a column of decimal(5,2), the result still shows only 2 +decimals to the right of the dot +3. After formatting, the missing leading zeroes will be padded +4. The underlying data will not be altered, but formatted results may likely be rounded +up (e.g. .2954 will be displayed as 0.30 if the user wants only two decimals) +5. For output params use SQLSRV_SQLTYPE_DECIMAL with the correct precision and scale +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $fieldScale, will show $fieldScale decimal digits + if ($formatDecimal > 0) { + $numDecimals = ($formatDecimal > $fieldScale) ? $fieldScale : $formatDecimal; + } else { + $numDecimals = $fieldScale; + } + $expected = number_format($input, $numDecimals); + if ($actual === $expected) { + $matched = true; + } else { + echo "For $column: expected $expected but the value is $actual\n"; + } + } + return $matched; +} + +function testErrorCases($conn) +{ + $query = "SELECT 0.0001"; + + $options = array('FormatDecimals' => true); + $stmt = sqlsrv_query($conn, $query, array(), $options); + if ($stmt) { + fatalError("Case 1: expected query to fail!!"); + } else { + $error = sqlsrv_errors()[0]['message']; + $message = 'Expected an integer to specify number of decimals to format the output values of decimal data types.'; + + if (strpos($error, $message) === false) { + print_r(sqlsrv_errors()); + } + } + + $options = array('FormatDecimals' => -1); + $stmt = sqlsrv_query($conn, $query, array(), $options); + if ($stmt) { + fatalError("Case 2: expected query to fail!!"); + } else { + $error = sqlsrv_errors()[0]['message']; + $message = 'For formatting decimal data values, -1 is out of range. Expected an integer from 0 to 38, inclusive.'; + + if (strpos($error, $message) === false) { + print_r(sqlsrv_errors()); + } + } +} + +function testMoneyTypes($conn) +{ + // With money and smallmoney types, which are essentially decimal types + // ODBC driver does not support Always Encrypted feature with money / smallmoney + $values = array('1.9954', '0', '-0.5', '0.2954', '9.6789', '99.991'); + $defaults = array('1.9954', '.0000', '-.5000', '.2954', '9.6789', '99.9910'); + + $query = "SELECT CONVERT(smallmoney, $values[0]), + CONVERT(money, $values[1]), + CONVERT(smallmoney, $values[2]), + CONVERT(money, $values[3]), + CONVERT(smallmoney, $values[4]), + CONVERT(money, $values[5])"; + + $stmt = sqlsrv_query($conn, $query); + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($values); $i++) { + if ($defaults[$i] !== $results[$i]) { + echo "testMoneyTypes: Expected default $defaults[$i] but got $results[$i]\n"; + } + } + + // Set FormatDecimals to 0 decimal digits + $numDigits = 0; + $options = array('FormatDecimals' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($values); $i++) { + $value = number_format($values[$i], $numDigits); + if ($value !== $results[$i]) { + echo "testMoneyTypes: Expected $value but got $results[$i]\n"; + } + } + + // Set FormatDecimals to 2 decimal digits + $numDigits = 2; + $options = array('FormatDecimals' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($values); $i++) { + $value = number_format($values[$i], $numDigits); + if ($value !== $results[$i]) { + echo "testMoneyTypes: Expected $value but got $results[$i]\n"; + } + } +} + +function testNoOption($conn, $tableName, $inputs, $columns, $exec) +{ + // Without the statement option, should return decimal values as they are + $query = "SELECT * FROM $tableName"; + if ($exec) { + $stmt = sqlsrv_query($conn, $query); + } else { + $stmt = sqlsrv_prepare($conn, $query); + sqlsrv_execute($stmt); + } + + // Compare values + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($inputs); $i++) { + compareNumbers($results[$i], $inputs[$i], $columns[$i], $i); + } +} + +function testStmtOption($conn, $tableName, $inputs, $columns, $formatDecimal, $withBuffer) +{ + // Decimal values should return decimal digits based on the valid statement + // option FormatDecimals + $query = "SELECT * FROM $tableName"; + if ($withBuffer){ + $options = array('Scrollable' => 'buffered', 'FormatDecimals' => $formatDecimal); + } else { + $options = array('FormatDecimals' => $formatDecimal); + } + + $size = count($inputs); + $stmt = sqlsrv_prepare($conn, $query, array(), $options); + + // Fetch by getting one field at a time + sqlsrv_execute($stmt); + + if (sqlsrv_fetch($stmt) === false) { + fatalError("Failed in retrieving data\n"); + } + for ($i = 0; $i < $size; $i++) { + $field = sqlsrv_get_field($stmt, $i); // Expect a string + compareNumbers($field, $inputs[$i], $columns[$i], $i, $formatDecimal); + } +} + +function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale) +{ + $outString = ''; + $numDigits = 2; + + // Derive the sql type SQLSRV_SQLTYPE_DECIMAL($prec, $scale) + $sqlType = call_user_func('SQLSRV_SQLTYPE_DECIMAL', $prec, $scale); + + $outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1); + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString, SQLSRV_PARAM_OUT, null, $sqlType)), + array('FormatDecimals' => $numDigits)); + if (!$stmt) { + fatalError("getOutputParam: failed when preparing to call $storedProcName"); + } + if (!sqlsrv_execute($stmt)) { + fatalError("getOutputParam: failed to execute procedure $storedProcName"); + } + + compareNumbers($outString, $inputValue, 'outputParam', $scale, $numDigits); +} + +function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes) +{ + for ($i = 0, $p = 3; $i < count($columns); $i++, $p++) { + // Create the stored procedure first + $storedProcName = "spFormatDecimals" . $i; + $procArgs = "@col $dataTypes[$i] OUTPUT"; + $procCode = "SELECT @col = $columns[$i] FROM $tableName"; + createProc($conn, $storedProcName, $procArgs, $procCode); + + // Call stored procedure to retrieve output param + getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i); + + dropProc($conn, $storedProcName); + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$conn = AE\connect(); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// First to test if leading zero is added +testMoneyTypes($conn); + +// Then test error conditions +testErrorCases($conn); + +// Create the test table of decimal / numeric data columns +$tableName = 'sqlsrvFormatDecimals'; + +$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); +$dataTypes = array('decimal(3,0)', 'decimal(4,1)', 'decimal(5,2)', 'numeric(6,3)', 'numeric(7,4)', 'numeric(8, 5)'); + +$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]), + new AE\ColumnMeta($dataTypes[1], $columns[1]), + new AE\ColumnMeta($dataTypes[2], $columns[2]), + new AE\ColumnMeta($dataTypes[3], $columns[3]), + new AE\ColumnMeta($dataTypes[4], $columns[4]), + new AE\ColumnMeta($dataTypes[5], $columns[5])); +AE\createTable($conn, $tableName, $colMeta); + +// Generate random input values based on precision and scale +$values = array(); +for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { + // First get a random number + $n = rand(0, 10); + $neg = ($n % 2 == 0) ? -1 : 1; + + // $n1 may or may not be negative + $max1 = 1000; + $n1 = rand(0, $max1) * $neg; + + if ($s > 0) { + $max2 = pow(10, $s); + $n2 = rand(0, $max2); + $number = sprintf("%d.%d", $n1, $n2); + } else { + $number = sprintf("%d", $n1); + } + + array_push($values, $number); +} + +if (traceMode()) { + var_dump($values); +} + +// Insert data values as strings +$inputData = array($colMeta[0]->colName => $values[0], + $colMeta[1]->colName => $values[1], + $colMeta[2]->colName => $values[2], + $colMeta[3]->colName => $values[3], + $colMeta[4]->colName => $values[4], + $colMeta[5]->colName => $values[5]); +$stmt = AE\insertRow($conn, $tableName, $inputData); +if (!$stmt) { + fatalError("Failed to insert data.\n"); +} +sqlsrv_free_stmt($stmt); + +testNoOption($conn, $tableName, $values, $columns, true); +testNoOption($conn, $tableName, $values, $columns, false); + +// Now try with setting number decimals to 3 then 2 +testStmtOption($conn, $tableName, $values, $columns, 3, false); +testStmtOption($conn, $tableName, $values, $columns, 3, true); + +testStmtOption($conn, $tableName, $values, $columns, 2, false); +testStmtOption($conn, $tableName, $values, $columns, 2, true); + +// Test output parameters +testOutputParam($conn, $tableName, $values, $columns, $dataTypes); + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt new file mode 100644 index 000000000..4abb0398d --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt @@ -0,0 +1,255 @@ +--TEST-- +Test various precisions of formatting decimal data output values (feature request issue 415) +--DESCRIPTION-- +In SQL Server, the maximum allowed precision is 38. The scale can range from 0 up to the +defined precision. Generate a long numeric string and get rid of the last digit to make it a +39-digit-string. Then replace one digit at a time with a dot '.' to make it a decimal +input string for testing with various scales. +For example, +string(39) ".23456789012345678901234567890123456789" +string(39) "1.3456789012345678901234567890123456789" +string(39) "12.456789012345678901234567890123456789" +string(39) "123.56789012345678901234567890123456789" +string(39) "1234.6789012345678901234567890123456789" +string(39) "12345.789012345678901234567890123456789" +... ... +string(39) "1234567890123456789012345678901234.6789" +string(39) "12345678901234567890123456789012345.789" +string(39) "123456789012345678901234567890123456.89" +string(39) "1234567890123456789012345678901234567.9" +string(38) "12345678901234567890123456789012345678" + +Note: PHP number_format() will not be used for verification in this test +because the function starts losing accuracy with large number of precisions / scales. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $digits)); + + // Restore the $i-th digit with its original digit + $digits[$i] = $d; + } + + $stmt = AE\insertRow($conn, $tableName, $inputData); + if (!$stmt) { + fatalError("Failed to insert data\n"); + } + sqlsrv_free_stmt($stmt); + + return $inputData; +} + +function verifyNoDecimals($value, $input, $round) +{ + global $prec, $dot; + + // Use PHP explode() to separate the input string into an array + $parts = explode($dot, $input); + $len = strlen($parts[0]); + if ($len == 0) { + // The original input string is missing a leading zero + $parts[0] = '0'; + } + + // No need to worry about carry over for the input data of this test + // Check the first digit of $parts[1] + if ($len < $prec) { + // Only need to round up when $len < $prec + $ch = $parts[1][0]; + + // Round the last digit of $parts[0] if $ch is '5' or above + if ($ch >= '5') { + $len = strlen($parts[0]); + $parts[0][$len-1] = $parts[0][$len-1] + 1 + '0'; + } + } + + // No decimal digits left in the expected string + $expected = $parts[0]; + if ($value !== $expected) { + echo "Round $round scale 0: expected $expected but returned $value\n"; + } +} + +function verifyWithDecimals($value, $input, $round, $scale) +{ + global $dot; + + // Use PHP explode() to separate the input string into an array + $parts = explode($dot, $input); + if (strlen($parts[0]) == 0) { + // The original input string is missing a leading zero + $parts[0] = '0'; + } + + // No need to worry about carry over for the input data of this test + // Check the digit at the position $scale of $parts[1] + $len = strlen($parts[1]); + if ($scale < $len) { + // Only need to round up when $scale < $len + $ch = $parts[1][$scale]; + + // Round the previous digit if $ch is '5' or above + if ($ch >= '5') { + $parts[1][$scale-1] = $parts[1][$scale-1] + 1 + '0'; + } + } + + // Use substr() to get up to $scale + $parts[1] = substr($parts[1], 0, $scale); + + // Join the array elements together + $expected = implode($dot, $parts); + if ($value !== $expected) { + echo "Round $round scale $scale: expected $expected but returned $value\n"; + } +} + +/**** +The function testVariousScales() will fetch one column at a time, using scale from +0 up to the maximum scale allowed for that column type. + +For example, for column of type decimal(38,4), the input string is +1234567890123456789012345678901234.6789 + +When fetching data, using scale from 0 to 4, the following values are expected to return: +1234567890123456789012345678901235 +1234567890123456789012345678901234.7 +1234567890123456789012345678901234.68 +1234567890123456789012345678901234.679 +1234567890123456789012345678901234.6789 + +For example, for column of type decimal(38,6), the input string is +12345678901234567890123456789012.456789 + +When fetching data, using scale from 0 to 6, the following values are expected to return: +12345678901234567890123456789012 +12345678901234567890123456789012.5 +12345678901234567890123456789012.46 +12345678901234567890123456789012.457 +12345678901234567890123456789012.4568 +12345678901234567890123456789012.45679 +12345678901234567890123456789012.456789 + +etc. +****/ +function testVariousScales($conn, $tableName, $inputData) +{ + global $prec; + $max = $prec + 1; + + for ($i = 0; $i < $max; $i++) { + $scale = $prec - $i; + $column = "col_$scale"; + + $query = "SELECT $column as col1 FROM $tableName"; + $input = $inputData[$column]; + + // Default case: the fetched value should be the same as the corresponding input + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("In testVariousScales: failed in default case\n"); + } + if ($obj = sqlsrv_fetch_object($stmt)) { + trace("\n$obj->col1\n"); + if ($obj->col1 !== $input) { + echo "default case: expected $input but returned $obj->col1\n"; + } + } else { + fatalError("In testVariousScales: sqlsrv_fetch_object failed\n"); + } + + // Next, format how many decimal digits to be displayed + $query = "SELECT $column FROM $tableName"; + for ($j = 0; $j <= $scale; $j++) { + $options = array('FormatDecimals' => $j); + $stmt = sqlsrv_query($conn, $query, array(), $options); + + if (sqlsrv_fetch($stmt)) { + $value = sqlsrv_get_field($stmt, 0); + trace("$value\n"); + + if ($j == 0) { + verifyNoDecimals($value, $input, $i); + } else { + verifyWithDecimals($value, $input, $i, $j); + } + } else { + fatalError("Round $i scale $j: sqlsrv_fetch failed\n"); + } + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$conn = AE\connect(); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +$tableName = createTestTable($conn); +$inputData = insertTestData($conn, $tableName); +testVariousScales($conn, $tableName, $inputData); + +dropTable($conn, $tableName); + +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file From 779376c7f0163be6814f3dadb099ed124dc3829d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 9 Oct 2018 16:05:45 -0700 Subject: [PATCH 3/7] Some refactoring --- source/shared/core_stmt.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 84ed3875c..3cc36a0cb 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2144,6 +2144,8 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT f str = oss.str(); pos++; } + + size_t last = 0; if (num_decimals == 0) { // Chop all decimal digits, including the decimal dot size_t pos2 = pos + 1; @@ -2170,14 +2172,7 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT f pos++; } } - - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0, pos); - str = oss.str(); - } else { - str = str.substr(0, pos); - } + last = pos; } else { size_t pos2 = pos + num_decimals + 1; @@ -2210,14 +2205,16 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT f } } } - - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0, pos2); - str = oss.str(); - } else { - str = str.substr(0, pos2); - } + last = pos2; + } + + // Add the minus sign back if negative + if (isNegative) { + std::ostringstream oss; + oss << '-' << str.substr(0, last); + str = oss.str(); + } else { + str = str.substr(0, last); } size_t len = str.length(); From cb8733460ed1ee8615762125385ea8e3325e168e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 9 Oct 2018 16:33:47 -0700 Subject: [PATCH 4/7] Fixed the inputs of the decimals tests --- .../sqlsrv/sqlsrv_statement_format_decimals.phpt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt index 9b48d207e..bbf6e4b58 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -238,6 +238,7 @@ AE\createTable($conn, $tableName, $colMeta); // Generate random input values based on precision and scale $values = array(); +$max2 = 1; for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { // First get a random number $n = rand(0, 10); @@ -248,7 +249,7 @@ for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { $n1 = rand(0, $max1) * $neg; if ($s > 0) { - $max2 = pow(10, $s); + $max2 *= 10; $n2 = rand(0, $max2); $number = sprintf("%d.%d", $n1, $n2); } else { @@ -258,10 +259,6 @@ for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { array_push($values, $number); } -if (traceMode()) { - var_dump($values); -} - // Insert data values as strings $inputData = array($colMeta[0]->colName => $values[0], $colMeta[1]->colName => $values[1], @@ -271,6 +268,7 @@ $inputData = array($colMeta[0]->colName => $values[0], $colMeta[5]->colName => $values[5]); $stmt = AE\insertRow($conn, $tableName, $inputData); if (!$stmt) { + var_dump($values); fatalError("Failed to insert data.\n"); } sqlsrv_free_stmt($stmt); From 666e124531b6c0e24322f2a957ff32d904abab0c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 10 Oct 2018 09:23:55 -0700 Subject: [PATCH 5/7] Added more test cases --- .../sqlsrv_statement_format_decimals.phpt | 70 +++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt index bbf6e4b58..539308729 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -25,7 +25,7 @@ PHPT_EXEC=true $fieldScale, will show $fieldScale decimal digits - if ($formatDecimal > 0) { + if ($formatDecimal >= 0) { $numDecimals = ($formatDecimal > $fieldScale) ? $fieldScale : $formatDecimal; } else { $numDecimals = $fieldScale; @@ -52,7 +52,7 @@ function testErrorCases($conn) { $query = "SELECT 0.0001"; - $options = array('FormatDecimals' => true); + $options = array('FormatDecimals' => 1.5); $stmt = sqlsrv_query($conn, $query, array(), $options); if ($stmt) { fatalError("Case 1: expected query to fail!!"); @@ -79,6 +79,47 @@ function testErrorCases($conn) } } +function testFloatTypes($conn) +{ + // This test with the float types of various number of bits, which are retrieved + // as numbers by default. When fetched as strings, no formatting is done even with + // the statement option FormatDecimals set + $values = array('2.9978', '-0.2982', '33.2434', '329.690734', '110.913498'); + $epsilon = 0.001; + + $query = "SELECT CONVERT(float(1), $values[0]), + CONVERT(float(12), $values[1]), + CONVERT(float(24), $values[2]), + CONVERT(float(36), $values[3]), + CONVERT(float(53), $values[4])"; + + $stmt = sqlsrv_query($conn, $query); + $floats = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + + // Set FormatDecimals to 2, but the number of decimals in each of the results + // will vary -- FormatDecimals has no effect + $numDigits = 2; + $options = array('FormatDecimals' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + if (sqlsrv_fetch($stmt)) { + for ($i = 0; $i < count($values); $i++) { + $floatStr = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + $numbers = explode('.', $floatStr); + $len = strlen($numbers[1]); + if ($len == $numDigits) { + // This is highly unlikely + var_dump($floatStr); + } + $floatVal = floatval($floatStr); + $diff = abs($floatVal - $floats[$i]) / $floats[$i]; + if ($diff > $epsilon) { + var_dump($diff); + var_dump($floatVal); + } + } + } +} + function testMoneyTypes($conn) { // With money and smallmoney types, which are essentially decimal types @@ -175,7 +216,7 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale) $outString = ''; $numDigits = 2; - // Derive the sql type SQLSRV_SQLTYPE_DECIMAL($prec, $scale) + // Derive the sqlsrv type SQLSRV_SQLTYPE_DECIMAL($prec, $scale) $sqlType = call_user_func('SQLSRV_SQLTYPE_DECIMAL', $prec, $scale); $outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1); @@ -189,7 +230,23 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale) fatalError("getOutputParam: failed to execute procedure $storedProcName"); } - compareNumbers($outString, $inputValue, 'outputParam', $scale, $numDigits); + // The output param should have been formatted based on $numDigits, if less + // than $scale + $column = 'outputParam'; + compareNumbers($outString, $inputValue, $column, $scale, $numDigits); + sqlsrv_free_stmt($stmt); + + // Get output param without specifying sqlsrv type, and the returned value will + // be a regular string -- its value should be the same as the input value, + // unaffected by the statement option FormatDecimals + $outString2 = ''; + $stmt = sqlsrv_query($conn, $outSql, + array(array(&$outString2, SQLSRV_PARAM_OUT)), + array('FormatDecimals' => $numDigits)); + + $column = 'outputParam2'; + compareNumbers($outString2, $inputValue, $column, $scale); + sqlsrv_free_stmt($stmt); } function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes) @@ -222,6 +279,9 @@ testMoneyTypes($conn); // Then test error conditions testErrorCases($conn); +// Also test using regular floats +testFloatTypes($conn); + // Create the test table of decimal / numeric data columns $tableName = 'sqlsrvFormatDecimals'; From a40002f0325b53d96cf9453896236b00886c0ad8 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 10 Oct 2018 11:30:13 -0700 Subject: [PATCH 6/7] Modified the decimal test to make it more robust --- .../sqlsrv_statement_format_decimals.phpt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt index 539308729..7e9e4b243 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -95,6 +95,9 @@ function testFloatTypes($conn) $stmt = sqlsrv_query($conn, $query); $floats = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + if (!$floats) { + echo "testFloatTypes: sqlsrv_fetch_array failed\n"; + } // Set FormatDecimals to 2, but the number of decimals in each of the results // will vary -- FormatDecimals has no effect @@ -117,6 +120,8 @@ function testFloatTypes($conn) var_dump($floatVal); } } + } else { + echo "testFloatTypes: sqlsrv_fetch failed\n"; } } @@ -236,17 +241,27 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale) compareNumbers($outString, $inputValue, $column, $scale, $numDigits); sqlsrv_free_stmt($stmt); - // Get output param without specifying sqlsrv type, and the returned value will - // be a regular string -- its value should be the same as the input value, - // unaffected by the statement option FormatDecimals - $outString2 = ''; - $stmt = sqlsrv_query($conn, $outSql, - array(array(&$outString2, SQLSRV_PARAM_OUT)), - array('FormatDecimals' => $numDigits)); - - $column = 'outputParam2'; - compareNumbers($outString2, $inputValue, $column, $scale); - sqlsrv_free_stmt($stmt); + if (!AE\isColEncrypted()) { + // Get output param without specifying sqlsrv type, and the returned value will + // be a regular string -- its value should be the same as the input value, + // unaffected by the statement option FormatDecimals + // With ColumnEncryption enabled, the driver is able to derive the decimal type, + // so skip this part of the test + $outString2 = ''; + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString2, SQLSRV_PARAM_OUT)), + array('FormatDecimals' => $numDigits)); + if (!$stmt) { + fatalError("getOutputParam2: failed when preparing to call $storedProcName"); + } + if (!sqlsrv_execute($stmt)) { + fatalError("getOutputParam2: failed to execute procedure $storedProcName"); + } + + $column = 'outputParam2'; + compareNumbers($outString2, $inputValue, $column, $scale); + sqlsrv_free_stmt($stmt); + } } function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes) From 33cacf51d07e64a14240a7b717fa7457d7f444ad Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 12 Oct 2018 14:26:48 -0700 Subject: [PATCH 7/7] Applied review comment --- source/shared/core_stmt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 3cc36a0cb..ae86d4884 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1427,12 +1427,12 @@ void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_o throw core::CoreException(); } - short format_decimals = static_cast(Z_LVAL_P(value_z)); + zend_long format_decimals = Z_LVAL_P(value_z); CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) { throw core::CoreException(); } - stmt->num_decimals = format_decimals; + stmt->num_decimals = static_cast(format_decimals); } // internal function to release the active stream. Called by each main API function