From b47cec284bf5a99a023e299067f10687922b7655 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 24 Sep 2018 13:26:43 -0700 Subject: [PATCH] Dev to Master - 5.4.0-preview (#853) * 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 * Fixed skipifs for connres * Tweaked per review comments * Changes made to source and tests to support PHP 7.3 (#822) * Changes made to support php 7.3 * Correct use of the smart pointer * Fixed the tests for 7.3 * Some clean up for array_init() * Fixed formattings and clean up * One more fix * Initialising strings with nulls * Removed some spaces * Made array index spacing consistent * Fix for compilation problem * Fix for compilation problem again * Before freeing stmt in destructor check if dbh driver data is NULL (#829) * Issue 434 - set dbh driver data to NULL as well in destructor * Reverted the last change but instead check if dbh driver_data is already freed * Modified the comment * Added driver to the skipif conditions (#831) * Used git clone instead to download source from a branch of a tag (#832) * Modified the error handling to make it more flexible (#833) * Made error handling more flexible * Fixed a minor issue with a test * Enabled Spectre Mitigations (#836) * Incorporated changes in PR 634 to pdo_sqlsrv (#834) * Incorporated changes in PR 634 to pdo_sqlsrv * Reverted the changes because the array is for internal use only * Modified README re user's suggestion (#841) * Modified README re user's suggestion * Moved the if condition to the end as per review * Adding supporting for Azure AD access token (#837) * Adding supporting for Azure AD access token * Added more comments for the AD access token skipif files * Save the pointer to access token struct until after connecting * Clear the access token data before freeing the memory * Added a reference as per review * Feature request - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects (#842) * Feature request - issue 648 * Fixed constructor for field_cache and added another test * Added tests for FETCH_BOUND * Added a new test for output param * Modified output param test to set attributes differently * Removed a useless helped function in a test * Combined two new tests into one as per review * Uncommented dropTable * Feature request - add ReturnDatesAsStrings option to statement level for sqlsrv (#844) * Added ReturnDatesAsStrings option to the statement level * Added new tests for ReturnDatesAsStrings at statement level * Added more datetime types as per review * Updated version 5.4.0-preview (#846) * Updated version 5.4.0-preview * Replaced 5.3 with 5.4 * Fixed sqlsrv datetime tests to connect with ColumnEncryption variables (#849) * Change log for 5.4.0-preview (#850) * Updated change log for 5.4.0-preview * Updated 5.4.0 preview to add two new feature requests * Modified change log as per review * Modified the wordings * Updated readme, changelog, and install instructions --- CHANGELOG.md | 35 +++ Linux-mac-install.md | 4 +- README.md | 32 +-- buildscripts/builddrivers.py | 4 +- buildscripts/buildtools.py | 37 +-- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 5 +- source/pdo_sqlsrv/pdo_dbh.cpp | 102 +++++--- source/pdo_sqlsrv/pdo_init.cpp | 9 +- source/pdo_sqlsrv/pdo_parser.cpp | 50 ++-- source/pdo_sqlsrv/pdo_stmt.cpp | 171 ++++++++----- source/pdo_sqlsrv/pdo_util.cpp | 15 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 13 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 12 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 145 ++++++++--- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 164 ++++++------ source/shared/core_sqlsrv.h | 60 +++-- source/shared/core_stmt.cpp | 47 ++-- source/shared/core_stream.cpp | 4 +- source/shared/core_util.cpp | 46 +++- source/shared/globalization.h | 4 +- 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 | 9 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 6 +- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 5 +- source/sqlsrv/conn.cpp | 45 ++-- source/sqlsrv/init.cpp | 8 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/stmt.cpp | 35 ++- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 65 +++-- test/functional/pdo_sqlsrv/access_token.inc | 3 + .../pdo_sqlsrv/pdo_azure_ad_access_token.phpt | 157 ++++++++++++ .../pdo_fetch_datetime_as_output_param.phpt | 87 +++++++ .../pdo_fetch_datetime_time_as_objects.phpt | 238 ++++++++++++++++++ .../pdo_fetch_datetime_time_nulls.phpt | 163 ++++++++++++ .../skipif_azure_ad_acess_token.inc | 41 +++ .../skipif_version_less_than_2k14.inc | 18 +- .../skipif_version_less_than_2k16.inc | 6 +- test/functional/sqlsrv/access_token.inc | 3 + .../sqlsrv/skipif_azure_ad_acess_token.inc | 45 ++++ .../sqlsrv/skipif_version_less_than_2k14.inc | 20 +- .../sqlsrv/skipif_version_less_than_2k16.inc | 6 +- .../sqlsrv_ae_type_conversion_select.phpt | 10 +- .../sqlsrv/sqlsrv_azure_ad_access_token.phpt | 131 ++++++++++ test/functional/sqlsrv/sqlsrv_errors.phpt | 6 +- .../sqlsrv_statement_datetimes_as_nulls.phpt | 120 +++++++++ ...sqlsrv_statement_datetimes_as_strings.phpt | 202 +++++++++++++++ ...lsrv_statement_datetimes_output_param.phpt | 113 +++++++++ test/functional/sqlsrv/test_conn_execute.phpt | 48 ++-- .../sqlsrv/test_non_alpha_password.phpt | 122 ++++----- 67 files changed, 2166 insertions(+), 545 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/access_token.inc create mode 100644 test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc create mode 100644 test/functional/sqlsrv/access_token.inc create mode 100644 test/functional/sqlsrv/skipif_azure_ad_acess_token.inc create mode 100644 test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index abaa24db..ecb2ee3f 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.4.0-preview - 2018-09-24 +Updated PECL release packages. Here is the list of updates: + +### Added +- Added support for PHP 7.3.0 RC 1 +- Added support for Azure AD Access Token (in Linux / macOS this requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [unixODBC](http://www.unixodbc.org/) 2.3.6+) +- Feature Request [#842](https://github.com/Microsoft/msphpsql/pull/842) - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects +- Feature Request [#844](https://github.com/Microsoft/msphpsql/pull/844) - add ReturnDatesAsStrings option to statement level for sqlsrv +- Compatible with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/) + +### Removed +- Dropped support for Ubuntu 17.10 +- Dropped support for PHP 7.0 - [Version 5.3](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver?view=sql-server-2017) is the last to support PHP 7.0. + +### Fixed +- Issue [#434](https://github.com/Microsoft/msphpsql/issues/434) - To avoid the pitfall that could result in a crash, before freeing stmt in the destructor check if its dbh driver data is NULL +- Pull Request [#836](https://github.com/Microsoft/msphpsql/pull/836) - Modified the config files to enable Spectre Mitigations (use /Qspectre switch) for PHP 7.2 +- Pull Request [#833](https://github.com/Microsoft/msphpsql/pull/833) - Streamlined the error handling to remove a potential cause of crash + +### Limitations +- No support for inout / output params when using sql_variant type +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) +- With ColumnEncryption enabled, fetching varbinary(max), varchar(max) or nvarchar(max) may fail with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/) + ## 5.3.0 - 2018-07-20 Updated PECL release packages. Here is the list of updates: diff --git a/Linux-mac-install.md b/Linux-mac-install.md index d84eca65..970bc3d0 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -5,13 +5,13 @@ These instructions install PHP 7.2 by default -- see the notes at the beginning ## Contents of this page: -- [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 Ubuntu 16.04 and 18.04](#installing-the-drivers-on-ubuntu-1604-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, 17.10 and 18.04 +## Installing the drivers on Ubuntu 16.04 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. diff --git a/README.md b/README.md index 20e3c0e9..071d582f 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ **Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server** -The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) to handle the low-level communication with SQL Server. +The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) to handle the low-level communication with SQL Server. -This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.* with improvements on both drivers and some limitations (see Limitations below for details). Upcoming releases will contain additional functionalities, bug fixes, and more. +This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some [limitations](https://github.com/Microsoft/msphpsql/releases). Upcoming releases will contain additional functionalities, bug fixes, and more. ## Take our survey @@ -28,11 +28,11 @@ Thank you for taking the time to participate in our last survey. You can continu ## Get Started -* [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/windows) -* [**Ubuntu + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/ubuntu) -* [**RedHat + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/rhel) -* [**SUSE + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/sles) -* [**macOS + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/mac/) +* [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/windows) +* [**Ubuntu + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/ubuntu) +* [**RedHat + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/rhel) +* [**SUSE + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/sles) +* [**macOS + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/mac/) * [**Docker**](https://hub.docker.com/r/lbosqmsft/mssql-php-msphpsql/) @@ -42,12 +42,12 @@ Thank you for taking the time to participate in our last survey. You can continu ## Prerequisites -For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/en-us/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs. +For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs. On the client machine: -- 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, 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) +- PHP 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) +- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) +- If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. @@ -57,16 +57,16 @@ The drivers are distributed as pre-compiled extensions for PHP found on the [rel If you choose to build the drivers, you must be able to build PHP 7 without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. -To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/en-us/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. +To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. -Finally, restart the Web server. +Finally, if running PHP in a Web server, restart the Web server. ## Install (UNIX) -For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs](https://docs.microsoft.com/en-us/sql/connect/php/installation-tutorial-linux-mac). +For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac). ## Sample Code -For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/en-us/sql/connect/php/code-samples-for-php-sql-driver). +For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver). ## Limitations and Known Issues Please refer to [Releases](https://github.com/Microsoft/msphpsql/releases) for the latest limitations and known issues. @@ -138,6 +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]: https://docs.microsoft.com/en-us/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 +[phpdoc]: https://docs.microsoft.com/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 [PHPMan]: http://php.net/manual/install.unix.php diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index 4378ad1f..e9ac02dc 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -239,7 +239,7 @@ if __name__ == '__main__': parser.add_argument('--DRIVER', default='all', choices=['all', 'sqlsrv', 'pdo_sqlsrv'], help="driver to build (default: all)") parser.add_argument('--DEBUG', action='store_true', help="enable debug mode (default: False)") parser.add_argument('--REPO', default='Microsoft', help="GitHub repository (default: Microsoft)") - parser.add_argument('--BRANCH', default='dev', help="GitHub repository branch (default: dev)") + parser.add_argument('--BRANCH', default='dev', help="GitHub repository branch or tag (default: dev)") parser.add_argument('--SOURCE', default=None, help="a local path to source file (default: None)") parser.add_argument('--TESTING', action='store_true', help="turns on testing mode (default: False)") parser.add_argument('--DESTPATH', default=None, help="an alternative destination for the drivers (default: None)") @@ -280,7 +280,7 @@ if __name__ == '__main__': answer = input("Download source from a GitHub repo? [y/n]: ") if answer == 'yes' or answer == 'y' or answer == '': repo = input("Name of the repo (hit enter for 'Microsoft'): ") - branch = input("Name of the branch (hit enter for 'dev'): ") + branch = input("Name of the branch or tag (hit enter for 'dev'): ") if repo == '': repo = 'Microsoft' if branch == '': diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 306aafaa..79b36923 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -193,7 +193,7 @@ class BuildUtil(object): file.write('@CALL ROBOCOPY ' + source + ' ' + dest + ' /s /xx /xo' + os.linesep) @staticmethod - def download_msphpsql_source(repo, branch, dest_folder = 'Source', clean_up = True): + def download_msphpsql_source(repo, branch, dest_folder = 'Source'): """Download to *dest_folder* the msphpsql archive of the specified GitHub *repo* and *branch*. The downloaded files will be removed by default. """ @@ -201,40 +201,25 @@ class BuildUtil(object): work_dir = os.path.dirname(os.path.realpath(__file__)) temppath = os.path.join(work_dir, 'temp') - if os.path.exists(temppath): - shutil.rmtree(temppath) - os.makedirs(temppath) + # There is no need to remove tree - + # for Bamboo, it will be cleaned up eventually + # for local development, this can act as a cached copy of the repo + if not os.path.exists(temppath): + os.makedirs(temppath) os.chdir(temppath) - file = branch + '.zip' - url = 'https://github.com/' + repo + '/msphpsql/archive/' + branch + '.zip' - - print('Downloading ' + url + ' ...') - try: - with urllib.request.urlopen(url) as response, open(file, 'wb') as out_file: - shutil.copyfileobj(response, out_file) - except: - print ("Resort to skip ssl verification...") - # need to skip ssl verification on some agents - # see https://www.python.org/dev/peps/pep-0476/ - with urllib.request.urlopen(url, context=ssl._create_unverified_context()) as response, open(file, 'wb') as out_file: - shutil.copyfileobj(response, out_file) - - print('Extracting ' + file + ' ...') - zip = zipfile.ZipFile(file) - zip.extractall() - zip.close() - msphpsqlFolder = os.path.join(temppath, 'msphpsql-' + branch) + + url = 'https://github.com/' + repo + '/msphpsql.git' + command = 'git clone ' + url + ' -b ' + branch + ' --single-branch --depth 1 ' + msphpsqlFolder + os.system(command) + source = os.path.join(msphpsqlFolder, 'source') os.chdir(work_dir) os.system('ROBOCOPY ' + source + '\shared ' + dest_folder + '\shared /xx /xo') os.system('ROBOCOPY ' + source + '\pdo_sqlsrv ' + dest_folder + '\pdo_sqlsrv /xx /xo') os.system('ROBOCOPY ' + source + '\sqlsrv ' + dest_folder + '\sqlsrv /xx /xo') - - if clean_up: - shutil.rmtree(temppath) except: print('Error occurred when downloading source') diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index b1bfd6e4..3de4fefc 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Microsoft Drivers 5.4 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 7066e625..cd6e3a06 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -36,6 +36,9 @@ if( PHP_PDO_SQLSRV != "no" ) { ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/Zi" ); if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" ); ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); + if (VCVERS >= 1913) { + ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre"); + } ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 33020556..b424866e 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -38,6 +38,7 @@ namespace PDOConnOptionNames { const char Server[] = "Server"; const char APP[] = "APP"; +const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; @@ -79,6 +80,7 @@ enum PDO_STMT_OPTIONS { PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, PDO_STMT_OPTION_EMULATE_PREPARES, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, + PDO_STMT_OPTION_FETCHES_DATETIME_TYPE }; // List of all the statement options supported by this driver. @@ -92,6 +94,7 @@ const stmt_option PDO_STMT_OPTS[] = { { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, + { NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr( new stmt_option_fetch_datetime ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -185,6 +188,15 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, + { + PDOConnOptionNames::AccessToken, + sizeof( PDOConnOptionNames::AccessToken ), + SQLSRV_CONN_OPTION_ACCESS_TOKEN, + ODBCConnOptions::AccessToken, + sizeof( ODBCConnOptions::AccessToken), + CONN_ATTR_STRING, + access_token_set_func::func + }, { PDOConnOptionNames::ApplicationIntent, sizeof( PDOConnOptionNames::ApplicationIntent ), @@ -464,6 +476,7 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { driver_dbh->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ char func[length+1]; \ + memset(func, '\0', length+1); \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ @@ -484,7 +497,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo direct_query( false ), query_timeout( QUERY_TIMEOUT_INVALID ), client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), - fetch_numeric( false ) + fetch_numeric( false ), + fetch_datetime( false ) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -546,7 +560,7 @@ int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_ ALLOC_HASHTABLE( pdo_conn_options_ht ); core::sqlsrv_zend_hash_init( *g_pdo_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, - ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); // Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error. dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_pdo_henv_cp, dbh->data_source, @@ -1050,6 +1064,10 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false; + break; + // Not supported case PDO_ATTR_FETCH_TABLE_NAMES: case PDO_ATTR_FETCH_CATALOG_NAMES: @@ -1201,6 +1219,12 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout break; } + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_datetime ); + break; + } + default: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); @@ -1270,7 +1294,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 ] = {'\0'}; + 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 ); } @@ -1379,11 +1403,15 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } // only change the encoding if quote is called from the statement level (which should only be called when a statement // is prepared with emulate prepared on) - if ( is_statement ) { - pdo_stmt_t *stmt = Z_PDO_STMT_P( object ); + if (is_statement) { + pdo_stmt_t *stmt = Z_PDO_STMT_P(object); + SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_dbh_quote: stmt object was null"); // set the encoding to be the encoding of the statement otherwise set to be the encoding of the dbh - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - if ( driver_stmt->encoding() != SQLSRV_ENCODING_INVALID ) { + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast(stmt->driver_data); + SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null"); + + if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) { encoding = driver_stmt->encoding(); } else { @@ -1391,18 +1419,20 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const encoding = driver_dbh->encoding(); } // get the placeholder at the current position in driver_stmt->placeholders ht + // Normally it's not a good idea to alter the internal pointer in a hashed array + // (see pull request 634 on GitHub) but in this case this is for internal use only zval* placeholder = NULL; - if (( placeholder = zend_hash_get_current_data( driver_stmt->placeholders )) != NULL && zend_hash_move_forward( driver_stmt->placeholders ) == SUCCESS && stmt->bound_params != NULL ) { + if ((placeholder = zend_hash_get_current_data(driver_stmt->placeholders)) != NULL && zend_hash_move_forward(driver_stmt->placeholders) == SUCCESS && stmt->bound_params != NULL) { pdo_bound_param_data* param = NULL; - if ( Z_TYPE_P( placeholder ) == IS_STRING ) { - param = reinterpret_cast( zend_hash_find_ptr( stmt->bound_params, Z_STR_P( placeholder ))); + if (Z_TYPE_P(placeholder) == IS_STRING) { + param = reinterpret_cast(zend_hash_find_ptr(stmt->bound_params, Z_STR_P(placeholder))); } - else if ( Z_TYPE_P( placeholder ) == IS_LONG) { - param = reinterpret_cast( zend_hash_index_find_ptr( stmt->bound_params, Z_LVAL_P( placeholder ))); + else if (Z_TYPE_P(placeholder) == IS_LONG) { + param = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_params, Z_LVAL_P(placeholder))); } - if ( NULL != param ) { - SQLSRV_ENCODING param_encoding = static_cast( Z_LVAL( param->driver_params )); - if ( param_encoding != SQLSRV_ENCODING_INVALID ) { + if (NULL != param) { + SQLSRV_ENCODING param_encoding = static_cast(Z_LVAL(param->driver_params)); + if (param_encoding != SQLSRV_ENCODING_INVALID) { encoding = param_encoding; } } @@ -1412,13 +1442,13 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const if ( encoding == SQLSRV_ENCODING_BINARY ) { // convert from char* to hex digits using os std::basic_ostringstream os; - for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { // if unquoted is < 0 or > 255, that means this is a non-ascii character. Translation from non-ascii to binary is not supported. // return an empty terminated string for now - if (( int )unquoted[ index ] < 0 || ( int )unquoted[ index ] > 255) { + if (( int )unquoted[index] < 0 || ( int )unquoted[index] > 255) { *quoted_len = 0; *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); - ( *quoted )[ 0 ] = '\0'; + ( *quoted )[0] = '\0'; return 1; } // when an int is < 16 and is appended to os, its hex representation which starts @@ -1427,7 +1457,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const if (( int )unquoted[index] < 16 ) { os << '0'; } - os << std::hex << ( int )unquoted[ index ]; + os << std::hex << ( int )unquoted[index]; } std::basic_string str_hex = os.str(); // each character is represented by 2 digits of hex @@ -1439,13 +1469,13 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); unsigned int out_current = 0; // insert '0x' - ( *quoted )[ out_current++ ] = '0'; - ( *quoted )[ out_current++ ] = 'x'; - for ( size_t index = 0; index < unquoted_str_len && unquoted_str[ index ] != '\0'; ++index ) { - ( *quoted )[ out_current++ ] = unquoted_str[ index ]; + ( *quoted )[out_current++] = '0'; + ( *quoted )[out_current++] = 'x'; + for ( size_t index = 0; index < unquoted_str_len && unquoted_str[index] != '\0'; ++index ) { + ( *quoted )[out_current++] = unquoted_str[index]; } // null terminator - ( *quoted )[ out_current ] = '\0'; + ( *quoted )[out_current] = '\0'; sqlsrv_free( unquoted_str ); return 1; } @@ -1457,7 +1487,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const quotes_needed = 3; } for ( size_t index = 0; index < unquoted_len; ++index ) { - if ( unquoted[ index ] == '\'' ) { + if ( unquoted[index] == '\'' ) { ++quotes_needed; } } @@ -1468,24 +1498,24 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const // insert N if the encoding is UTF8 if ( encoding == SQLSRV_ENCODING_UTF8 ) { - ( *quoted )[ out_current++ ] = 'N'; + ( *quoted )[out_current++] = 'N'; } // insert initial quote - ( *quoted )[ out_current++ ] = '\''; + ( *quoted )[out_current++] = '\''; for ( size_t index = 0; index < unquoted_len; ++index ) { - if ( unquoted[ index ] == '\'' ) { - ( *quoted )[ out_current++ ] = '\''; - ( *quoted )[ out_current++ ] = '\''; + if ( unquoted[index] == '\'' ) { + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current++] = '\''; } else { - ( *quoted )[ out_current++ ] = unquoted[ index ]; + ( *quoted )[out_current++] = unquoted[index]; } } // trailing quote and null terminator - ( *quoted )[ out_current++ ] = '\''; - ( *quoted )[ out_current ] = '\0'; + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current] = '\0'; return 1; } @@ -1552,6 +1582,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE; + break; + default: CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { throw core::CoreException(); diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 65e7a078..a5fcbdd0 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -132,10 +132,10 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); ::zend_hash_init( g_pdo_errors_ht, 50, NULL, pdo_error_dtor /*pDestructor*/, 1 ); - for( int i = 0; PDO_ERRORS[ i ].error_code != -1; ++i ) { + for( int i = 0; PDO_ERRORS[i].error_code != -1; ++i ) { - void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, - &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); + void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[i].error_code, + &( PDO_ERRORS[i].sqlsrv_error ), sizeof( PDO_ERRORS[i].sqlsrv_error ) ); if( zr == NULL ) { LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); @@ -285,6 +285,7 @@ namespace { { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, + { "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE }, // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, // PDO::PARAM_STR uses the size of the string in the variable diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index baafeed7..e6a70d4d 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -108,7 +108,7 @@ bool string_parser::discard_white_spaces() return false; } - while( this->is_white_space( this->orig_str[ pos ] )) { + while( this->is_white_space( this->orig_str[pos] )) { if( !next() ) return false; @@ -148,13 +148,13 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino { int new_len = discard_trailing_white_spaces( key, key_len ); - for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + for( int i=0; PDO_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) { // discard the null terminator. - if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { + if( new_len == ( PDO_CONN_OPTS[i].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[i].sqlsrv_name, new_len )) { - this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; - this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; + this->current_key = PDO_CONN_OPTS[i].conn_option_key; + this->current_key_name = PDO_CONN_OPTS[i].sqlsrv_name; return; } } @@ -164,7 +164,7 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino key_name = static_cast( sqlsrv_malloc( new_len + 1 )); memcpy_s( key_name, new_len + 1 ,key, new_len ); - key_name[ new_len ] = '\0'; + key_name[new_len] = '\0'; THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); } @@ -232,7 +232,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) start_pos = this->pos; // read the key name - while( this->orig_str[ pos ] != '=' ) { + while( this->orig_str[pos] != '=' ) { if( !next() ) { @@ -240,7 +240,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } } - this->validate_key( &( this->orig_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); + this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) TSRMLS_CC ); state = Value; @@ -249,13 +249,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) case Value: { - SQLSRV_ASSERT(( this->orig_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: " + SQLSRV_ASSERT(( this->orig_str[pos] == '=' ), "conn_string_parser:: parse_conn_string: " "Equal was expected" ); next(); // skip "=" // if EOS encountered after 0 or more spaces OR semi-colon encountered. - if( !discard_white_spaces() || this->orig_str[ pos ] == ';' ) { + if( !discard_white_spaces() || this->orig_str[pos] == ';' ) { add_key_value_pair( NULL, 0 TSRMLS_CC ); @@ -265,13 +265,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } else { - // this->orig_str[ pos ] == ';' + // this->orig_str[pos] == ';' state = NextKeyValuePair; } } // if LCB - else if( this->orig_str[ pos ] == '{' ) { + else if( this->orig_str[pos] == '{' ) { start_pos = this->pos; // starting character is LCB state = ValueContent1; @@ -289,7 +289,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) case ValueContent1: { - while ( this->orig_str[ pos ] != '}' ) { + while ( this->orig_str[pos] != '}' ) { if ( ! next() ) { @@ -305,7 +305,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) case ValueContent2: { - while( this->orig_str[ pos ] != ';' ) { + while( this->orig_str[pos] != ';' ) { if( ! next() ) { @@ -313,13 +313,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } } - if( !this->is_eos() && this->orig_str[ pos ] == ';' ) { + if( !this->is_eos() && this->orig_str[pos] == ';' ) { // semi-colon encountered, so go to next key-value pair state = NextKeyValuePair; } - add_key_value_pair( &( this->orig_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), "conn_string_parser::parse_conn_string: Invalid state encountered " ); @@ -334,14 +334,14 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) if( !next() ) { // EOS - add_key_value_pair( &( this->orig_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); break; } SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" ); // if second RCB encountered than go back to ValueContent1 - if( this->orig_str[ pos ] == '}' ) { + if( this->orig_str[pos] == '}' ) { if( !next() ) { @@ -356,20 +356,20 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) int end_pos = this->pos; // discard any trailing white-spaces. - if( this->is_white_space( this->orig_str[ pos ] )) { + if( this->is_white_space( this->orig_str[pos] )) { if( ! this->discard_white_spaces() ) { //EOS - add_key_value_pair( &( this->orig_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos TSRMLS_CC ); break; } } // if semi-colon than go to next key-value pair - if ( this->orig_str[ pos ] == ';' ) { + if ( this->orig_str[pos] == ';' ) { - add_key_value_pair( &( this->orig_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos TSRMLS_CC ); state = NextKeyValuePair; break; } @@ -380,7 +380,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } case NextKeyValuePair: { - SQLSRV_ASSERT(( this->orig_str[ pos ] == ';' ), + SQLSRV_ASSERT(( this->orig_str[pos] == ';' ), "conn_string_parser::parse_conn_string: semi-colon was expected." ); // Call next() to skip the semi-colon. @@ -390,7 +390,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) break; } - if( this->orig_str[ pos ] == ';' ) { + if( this->orig_str[pos] == ';' ) { // a second semi-colon is error case. THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index d41dde3b..3f6bc328 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -51,6 +51,9 @@ inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori ( _In_ enum pdo_fetch_orienta // for list of supported pdo types. SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type TSRMLS_DC ) { + pdo_sqlsrv_stmt *pdo_stmt = static_cast(driver_stmt); + SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null"); + switch( pdo_type ) { case PDO_PARAM_BOOL: @@ -64,9 +67,12 @@ SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _I return SQLSRV_PHPTYPE_NULL; case PDO_PARAM_LOB: - // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. - return SQLSRV_PHPTYPE_STRING; - + if (pdo_stmt->fetch_datetime) { + return SQLSRV_PHPTYPE_DATETIME; + } else { + // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. + return SQLSRV_PHPTYPE_STRING; + } case PDO_PARAM_STMT: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); break; @@ -213,61 +219,63 @@ void meta_data_free( _Inout_ field_meta_data* meta ) zval convert_to_zval( _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len ) { zval out_zval; - ZVAL_UNDEF( &out_zval ); + ZVAL_UNDEF(&out_zval); - switch( sqlsrv_php_type ) { - - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - { - if( *in_val == NULL ) { - ZVAL_NULL( &out_zval ); + switch (sqlsrv_php_type) { + + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + { + if (*in_val == NULL) { + ZVAL_NULL(&out_zval); + } + else { + + if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { + ZVAL_LONG(&out_zval, **(reinterpret_cast(in_val))); } else { - - if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( &out_zval, **( reinterpret_cast( in_val ))); - } - else { - ZVAL_DOUBLE( &out_zval, **( reinterpret_cast( in_val ))); - } + ZVAL_DOUBLE(&out_zval, **(reinterpret_cast(in_val))); } - - if( *in_val ) { - sqlsrv_free( *in_val ); - } - - break; } - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented - { - - if( *in_val == NULL ) { - - ZVAL_NULL( &out_zval ); - } - else { - - ZVAL_STRINGL( &out_zval, reinterpret_cast( *in_val ), field_len ); - sqlsrv_free( *in_val ); - } - break; + if (*in_val) { + sqlsrv_free(*in_val); } - - case SQLSRV_PHPTYPE_DATETIME: - DIE( "Unsupported php type" ); - out_zval = *( reinterpret_cast( *in_val )); - break; - case SQLSRV_PHPTYPE_NULL: - ZVAL_NULL( &out_zval ); - break; + break; + } + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented + { + if (*in_val == NULL) { - default: - DIE( "Unknown php type" ); - break; + ZVAL_NULL(&out_zval); + } + else { + + ZVAL_STRINGL(&out_zval, reinterpret_cast(*in_val), field_len); + sqlsrv_free(*in_val); + } + break; + } + case SQLSRV_PHPTYPE_DATETIME: + if (*in_val == NULL) { + + ZVAL_NULL(&out_zval); + } + else { + + out_zval = *(reinterpret_cast(*in_val)); + sqlsrv_free(*in_val); + } + break; + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL(&out_zval); + break; + default: + DIE("Unknown php type"); + break; } return out_zval; @@ -339,6 +347,11 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; } +void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_datetime = ( zend_is_true( value_z )) ? true : false; +} // log a function entry point #ifndef _WIN32 @@ -348,6 +361,7 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt driver_stmt->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ char func[length+1]; \ + memset(func, '\0', length+1); \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ @@ -495,8 +509,13 @@ int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" ); // if a PDO statement didn't complete preparation, its driver_data can be NULL - if( driver_stmt == NULL ) { + if (driver_stmt == NULL) { + return 1; + } + // occasionally stmt->dbh->driver_data is already freed and reset but its driver_data is not + if (stmt->dbh != NULL && stmt->dbh->driver_data == NULL) { + stmt->driver_data = NULL; return 1; } @@ -554,12 +573,15 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the // subtituted query provided by PDO - if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) { // reset the placeholders hashtable internal in case the user reexecutes a statement + // Normally it's not a good idea to alter the internal pointer in a hashed array + // (see pull request 634 on GitHub) but in this case this is for internal use only + zend_hash_internal_pointer_reset(driver_stmt->placeholders); query = stmt->active_query_string; - query_len = static_cast( stmt->active_query_stringlen ); + query_len = static_cast(stmt->active_query_stringlen); } SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); @@ -651,13 +673,13 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta if (NULL== (bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, i))) && (NULL == (bind_data = reinterpret_cast(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) { - driver_stmt->bound_column_param_types[ i ] = PDO_PARAM_ZVAL; + driver_stmt->bound_column_param_types[i] = PDO_PARAM_ZVAL; continue; } if( bind_data->param_type != PDO_PARAM_ZVAL ) { - driver_stmt->bound_column_param_types[ i ] = bind_data->param_type; + driver_stmt->bound_column_param_types[i] = bind_data->param_type; bind_data->param_type = PDO_PARAM_ZVAL; } } @@ -744,10 +766,10 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, // if a column is bound to a type different than the column type, figure out a way to convert it to the // type they want - if( stmt->bound_columns && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_ZVAL ) { + if( stmt->bound_columns && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_ZVAL ) { sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, - driver_stmt->bound_column_param_types[ colno ] + driver_stmt->bound_column_param_types[colno] TSRMLS_CC ); pdo_bound_param_data* bind_data = NULL; @@ -764,8 +786,8 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, throw pdo::PDOException(); } - CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR - && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt, + CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[colno] != PDO_PARAM_STR + && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_LOB, driver_stmt, PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) { throw pdo::PDOException(); @@ -856,6 +878,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In driver_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false; + break; + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -937,6 +963,12 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; } + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_datetime ); + break; + } + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -991,7 +1023,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno add_assoc_long( return_value, "flags", 0 ); // get the name of the data type - char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + char field_type_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'}; SQLSMALLINT out_buff_len; SQLLEN not_used; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, @@ -1017,13 +1049,13 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec - char table_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + char table_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'}; SQLLEN field_type_num; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, &out_buff_len, &field_type_num TSRMLS_CC ); add_assoc_string( return_value, "table", table_name ); - if( stmt->columns && stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) { + if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) { add_assoc_long( return_value, "pdo_type", pdo_type ); } @@ -1356,6 +1388,17 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; } break; + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + if ( this->fetch_datetime ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + } + break; case SQL_BIGINT: case SQL_CHAR: case SQL_DECIMAL: @@ -1364,10 +1407,6 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, case SQL_WCHAR: case SQL_VARCHAR: case SQL_WVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: case SQL_LONGVARCHAR: case SQL_WLONGVARCHAR: case SQL_SS_XML: diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index a05e7547..dee2e3e0 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -37,7 +37,7 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ]; +char log_msg[LOG_MSG_SIZE] = {'\0'}; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; @@ -429,6 +429,15 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, { IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -89, false} }, + { + SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -90, false} + }, + { + SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false} + }, + { UINT_MAX, {} } }; @@ -512,7 +521,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned msg = static_cast( sqlsrv_malloc( msg_len ) ); core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, error->native_message ); - php_error( E_WARNING, msg ); + php_error(E_WARNING, "%s", msg.get()); } ctx.set_last_error( error ); break; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 38e4cec4..c110a9c6 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -48,6 +48,7 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_CURSOR_SCROLL_TYPE, SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, + SQLSRV_ATTR_FETCHES_DATETIME_TYPE }; // valid set of values for TransactionIsolation connection option @@ -203,6 +204,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { long query_timeout; zend_long client_buffer_max_size; bool fetch_numeric; + bool fetch_datetime; pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); }; @@ -241,6 +243,10 @@ struct stmt_option_fetch_numeric : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_fetch_datetime : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; // a core layer pdo stmt object. This object inherits and overrides the callbacks necessary @@ -253,11 +259,13 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { direct_query_subst_string_len( 0 ), placeholders(NULL), bound_column_param_types( NULL ), - fetch_numeric( false ) + fetch_numeric( false ), + fetch_datetime( false ) { pdo_sqlsrv_dbh* db = static_cast( c ); direct_query = db->direct_query; fetch_numeric = db->fetch_numeric; + fetch_datetime = db->fetch_datetime; } virtual ~pdo_sqlsrv_stmt( void ); @@ -275,6 +283,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { std::vector > current_meta_data; pdo_param_type* bound_column_param_types; bool fetch_numeric; + bool fetch_datetime; }; diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 8b94ddfa..435fa9a3 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 1b48c0e9..a5033db4 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -254,7 +254,7 @@ errno_t mplat_wctomb_s( void _CFLTCVT( double * dbl, char * buf, int bufSize, char fmt, int precision, int caps, _locale_t loc = NULL ) { const size_t local_bufsize = 8; - char local_fmt[local_bufsize]; + char local_fmt[local_bufsize] = {'\0'}; if ( 0 != caps ) { @@ -387,7 +387,7 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v textlen is in multibyte or wide chars if _UNICODE */ union { char sz[BUFFERSIZE]; - } buffer; + } buffer = {'\0'}; WCHAR wchar; /* temp wchar_t */ int buffersize; /* size of text.sz (used only for the call to _cfltcvt) */ int bufferiswide=0; /* non-zero = buffer contains wide chars already */ @@ -905,7 +905,7 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v WCHAR *p; int retval, count; errno_t e = 0; - char L_buffer[MB_LEN_MAX+1]; + char L_buffer[MB_LEN_MAX+1] = {'\0'}; p = text.wz; count = textlen; @@ -1221,7 +1221,7 @@ static DWORD FormatMessageToBufferA( const char * format, char * buffer, DWORD b DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); DWORD msg_pos = 0; const DWORD fmtsize = 32; - char fmt[fmtsize]; + char fmt[fmtsize] = {'\0'}; DWORD fmt_pos; char fmt_ch; @@ -1429,7 +1429,7 @@ DWORD FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD d *((char**)lpBuffer) = NULL; const DWORD max_size = 64000; - char local_buf[max_size]; + char local_buf[max_size] = {'\0'}; chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), local_buf, max_size, args ); if ( 0 < chars_printed ) { diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 87f15f46..f7933f97 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 d5183fc5..354ebbf6 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 b12e789b..475cd84c 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 b094e110..da6284dc 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -120,11 +120,11 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. #ifndef _WIN32 - char pooling_string[ 128 ] = {0}; + char pooling_string[128] = {'\0'}; SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" ); - if ( pooling_string[ 0 ] == '1' || toupper( pooling_string[ 0 ] ) == 'Y' || - ( toupper( pooling_string[ 0 ] ) == 'O' && toupper( pooling_string[ 1 ] ) == 'N' )) + if ( pooling_string[0] == '1' || toupper( pooling_string[0] ) == 'Y' || + ( toupper( pooling_string[0] ) == 'O' && toupper( pooling_string[1] ) == 'N' )) { henv = &henv_cp; is_pooled = true; @@ -243,6 +243,12 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont } // else driver_version not unknown #endif // !_WIN32 + // time to free the access token, if not null + if (conn->azure_ad_access_token != NULL) { + memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory + conn->azure_ad_access_token.reset(); + } + CHECK_SQL_ERROR( r, conn ) { throw core::CoreException(); } @@ -310,7 +316,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ if( SQL_SUCCEEDED( rc ) ) return false; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { 0 }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'}; SQLSMALLINT len; SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); @@ -327,7 +333,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version ) { #ifndef _WIN32 - char szBuf[DEFAULT_CONN_STR_LEN+1]; // use a large enough buffer size + char szBuf[DEFAULT_CONN_STR_LEN+1] = {'\0'}; // use a large enough buffer size WORD cbBufMax = DEFAULT_CONN_STR_LEN; WORD cbBufOut; char *pszBuf = szBuf; @@ -695,7 +701,7 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v { // if the value is already quoted, then only analyse the part inside the quotes and return it as // unquoted since we quote it when adding it to the connection string. - if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) { + if( value_len > 0 && value[0] == '{' && value[value_len - 1] == '}' ) { ++value; value_len -= 2; } @@ -736,11 +742,11 @@ namespace { connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN key, _In_ const connection_option conn_opts[] TSRMLS_DC ) { - for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { + for( int opt_idx = 0; conn_opts[opt_idx].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { - if( key == conn_opts[ opt_idx ].conn_option_key ) { + if( key == conn_opts[opt_idx].conn_option_key ) { - return &conn_opts[ opt_idx ]; + return &conn_opts[opt_idx]; } } @@ -759,35 +765,53 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou { bool mars_mentioned = false; connection_option const* conn_opt; + bool access_token_used = false; try { + // First of all, check if access token is specified. If so, check if UID, PWD, Authentication exist + // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers + if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { + bool invalidOptions = false; - // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); + // UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string, + // even if they may be empty strings. Likewise if the keyword Authentication exists + if (uid != NULL || pwd != NULL || zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION)) { + invalidOptions = true; + } - // if uid is not present then we use trusted connection. - if(uid == NULL || strnlen_s( uid ) == 0 ) { - - connection_string += "Trusted_Connection={Yes};"; - } - else { - - bool escaped = core_is_conn_opt_value_escaped( uid, strnlen_s( uid )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + CHECK_CUSTOM_ERROR(invalidOptions, conn, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) { throw core::CoreException(); } - common_conn_str_append_func( ODBCConnOptions::UID, uid, strnlen_s( uid ), connection_string TSRMLS_CC ); + access_token_used = true; + } - // if no password was given, then don't add a password to the connection string. Perhaps the UID - // given doesn't have a password? - if( pwd != NULL ) { - escaped = core_is_conn_opt_value_escaped( pwd, strnlen_s( pwd )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + // Add the server name + common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); + + // if uid is not present then we use trusted connection -- but not when access token is used, because they are incompatible + if (!access_token_used) { + if (uid == NULL || strnlen_s(uid) == 0) { + connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};" + } + else { + bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid)); + CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { throw core::CoreException(); } - common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strnlen_s( pwd ), connection_string TSRMLS_CC ); + common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string TSRMLS_CC); + + // if no password was given, then don't add a password to the connection string. Perhaps the UID + // given doesn't have a password? + if (pwd != NULL) { + escaped = core_is_conn_opt_value_escaped(pwd, strnlen_s(pwd)); + CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { + throw core::CoreException(); + } + + common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string TSRMLS_CC); + } } } @@ -919,15 +943,15 @@ const char* get_processor_arch( void ) void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) { SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ]; + char p[INFO_BUFFER_LEN] = {'\0'}; core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); errno = 0; - char version_major_str[ 3 ]; + char version_major_str[3] = {'\0'}; SERVER_VERSION version_major; memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - version_major_str[ 2 ] = '\0'; + version_major_str[2] = {'\0'}; version_major = static_cast( atoi( version_major_str )); CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) @@ -1025,7 +1049,7 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l // be escaped, such as a closing }. TSRMLS_C; - if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { + if( val_len > 0 && val[0] == '{' && val[val_len - 1] == '}' ) { ++val; val_len -= 2; } @@ -1151,8 +1175,8 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z ) // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) size_t last_char = val_len - 1; - while( isspace(( unsigned char )value_in[ last_char ] )) { - value_in[ last_char ] = '\0'; + while( isspace(( unsigned char )value_in[last_char] )) { + value_in[last_char] = '\0'; val_len = last_char; --last_char; } @@ -1172,3 +1196,56 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z ) return 0; // false } + +void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ) +{ + SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "An access token must be a byte string."); + + size_t value_len = Z_STRLEN_P(value); + + CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN) { + throw core::CoreException(); + } + + const char* value_str = Z_STRVAL_P( value ); + + // The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from + // an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also + // bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the + // SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure + // + // typedef struct AccessToken + // { + // unsigned int dataSize; + // char data[]; + // } ACCESSTOKEN; + // + // NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows. + // + // A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte, + // similar to a UCS-2 string containing only ASCII characters + // + // See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token + + size_t dataSize = 2 * value_len; + + sqlsrv_malloc_auto_ptr accToken; + accToken = reinterpret_cast(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize)); + + ACCESSTOKEN *pAccToken = accToken.get(); + SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token."); + + pAccToken->dataSize = dataSize; + + // Expand access token with padding bytes + for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) { + pAccToken->data[i] = value_str[j]; + pAccToken->data[i+1] = 0; + } + + core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast(pAccToken), SQL_IS_POINTER); + + // Save the pointer because SQLDriverConnect() will use it to make connection to the server + conn->azure_ad_access_token = pAccToken; + accToken.transferred(); +} diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index cda38fa5..274c10d2 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 62a757d8..67a80555 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -341,8 +341,8 @@ struct row_dtor_closure { sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record_number ) { - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ]; + SQLWCHAR wsql_state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; + SQLWCHAR wnative_message[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {L'\0'}; SQLINTEGER native_code; SQLSMALLINT wnative_message_len = 0; @@ -459,29 +459,29 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm // set up the conversion matrix if this is the first time we're called if( conv_matrix.size() == 0 ) { - conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double; - conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long; - conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double; - conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string; + conv_matrix[SQL_C_CHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[SQL_C_CHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::system_to_wide_string; + conv_matrix[SQL_C_CHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[SQL_C_CHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::string_to_double; + conv_matrix[SQL_C_CHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::string_to_long; + conv_matrix[SQL_C_WCHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[SQL_C_WCHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[SQL_C_WCHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::wide_to_system_string; + conv_matrix[SQL_C_WCHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::wstring_to_double; + conv_matrix[SQL_C_WCHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::wstring_to_long; + conv_matrix[SQL_C_BINARY][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[SQL_C_BINARY][SQL_C_CHAR] = &sqlsrv_buffered_result_set::binary_to_system_string; + conv_matrix[SQL_C_BINARY][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::binary_to_wide_string; + conv_matrix[SQL_C_LONG][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::long_to_double; + conv_matrix[SQL_C_LONG][SQL_C_LONG] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[SQL_C_LONG][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[SQL_C_LONG][SQL_C_CHAR] = &sqlsrv_buffered_result_set::long_to_system_string; + conv_matrix[SQL_C_LONG][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::long_to_wide_string; + conv_matrix[SQL_C_DOUBLE][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[SQL_C_DOUBLE][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[SQL_C_DOUBLE][SQL_C_CHAR] = &sqlsrv_buffered_result_set::double_to_system_string; + conv_matrix[SQL_C_DOUBLE][SQL_C_LONG] = &sqlsrv_buffered_result_set::double_to_long; + conv_matrix[SQL_C_DOUBLE][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::double_to_wide_string; } SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : @@ -687,7 +687,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { out_buffer_length = &out_buffer_temp; - SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); + SQLPOINTER* lob_addr = reinterpret_cast( &row[meta[i].offset] ); *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); // a NULL pointer means NULL field if( *lob_addr == NULL ) { @@ -854,7 +854,7 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ return SQL_ERROR; } - return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, + return (( this )->*( conv_matrix[meta[field_index].c_type][target_type] ))( field_index, buffer, buffer_length, out_buffer_length ); } @@ -965,9 +965,9 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so // to get the number of hex digits we can copy SQLLEN to_copy_hex = to_copy / (2 * extra); for( SQLLEN i = 0; i < to_copy_hex; ++i ) { - *h = hex_chars[ (*b & 0xf0) >> 4 ]; + *h = hex_chars[(*b & 0xf0) >> 4]; h++; - *h = hex_chars[ (*b++ & 0x0f) ]; + *h = hex_chars[(*b++ & 0x0f)]; h++; } read_so_far += to_copy_hex; @@ -986,13 +986,13 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( _In_ SQLSMALLINT SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); } else { - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ); } return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); @@ -1004,13 +1004,13 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( _In_ SQLSMALLINT fi SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); } else { - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ); } return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); @@ -1020,12 +1020,12 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( _In_ SQLSMALLINT fi SQLRETURN sqlsrv_buffered_result_set::double_to_long( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "Buffer length must be able to find a long in " "sqlsrv_buffered_result_set::double_to_long" ); unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); LONG* long_data = reinterpret_cast( buffer ); if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { @@ -1049,11 +1049,11 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_long( _In_ SQLSMALLINT field_ind SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" ); unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1066,11 +1066,11 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1083,12 +1083,12 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( _In_ SQLSMALLINT fi SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" ); unsigned char* row = get_row(); double* double_data = reinterpret_cast( buffer ); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); *double_data = static_cast( *long_data ); *out_buffer_length = sizeof( double ); @@ -1098,11 +1098,11 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_ind SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to system string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" ); unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1115,11 +1115,11 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT fi SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1132,49 +1132,49 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( _In_ SQLSMALLINT fiel SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1189,15 +1189,15 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT fi SQLCHAR* field_data = NULL; SQLULEN field_len = 0; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + field_len = **reinterpret_cast( &row[meta[field_index].offset] ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far; } else { - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + field_len = *reinterpret_cast( &row[meta[field_index].offset] ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ) + read_so_far; } // all fields will be treated as ODBC returns varchar(max) fields: @@ -1264,7 +1264,7 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT fi return SQL_ERROR; } - ((WCHAR*)buffer)[ to_copy ] = L'\0'; + ((WCHAR*)buffer)[to_copy] = L'\0'; read_so_far += to_copy; break; @@ -1288,7 +1288,7 @@ SQLRETURN sqlsrv_buffered_result_set::to_same_string( _In_ SQLSMALLINT field_ind // Set the amount of space necessary for null characters at the end of the data. SQLSMALLINT extra = 0; - switch( meta[ field_index ].c_type ) { + switch( meta[field_index].c_type ) { case SQL_C_WCHAR: extra = sizeof( SQLWCHAR ); break; @@ -1305,13 +1305,13 @@ SQLRETURN sqlsrv_buffered_result_set::to_same_string( _In_ SQLSMALLINT field_ind SQLCHAR* field_data = NULL; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); } else { - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ); } // all fields will be treated as ODBC returns varchar(max) fields: @@ -1366,15 +1366,15 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( _In_ SQLSMALLINT fi if( read_so_far == 0 ) { - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + field_len = **reinterpret_cast( &row[meta[field_index].offset] ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far; } else { - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + field_len = *reinterpret_cast( &row[meta[field_index].offset] ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ) + read_so_far; } if ( field_len == 0 ) { // empty string, no need for conversion @@ -1434,7 +1434,7 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( _In_ SQLSMALLINT fi } SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); - ((SQLCHAR*) buffer)[ to_copy ] = '\0'; + ((SQLCHAR*) buffer)[to_copy] = '\0'; read_so_far += to_copy; return r; @@ -1450,11 +1450,11 @@ SQLRETURN sqlsrv_buffered_result_set::to_binary_string( _In_ SQLSMALLINT field_i SQLRETURN sqlsrv_buffered_result_set::to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); *out_buffer_length = sizeof( LONG ); @@ -1464,11 +1464,11 @@ SQLRETURN sqlsrv_buffered_result_set::to_long( _In_ SQLSMALLINT field_index, _Ou SQLRETURN sqlsrv_buffered_result_set::to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to double" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); memcpy_s( buffer, buffer_length, double_data, sizeof( double )); *out_buffer_length = sizeof( double ); @@ -1489,7 +1489,7 @@ void cache_row_dtor( _In_ zval* data ) if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] ); + void* out_of_row_data = *reinterpret_cast( &row[result_set->col_meta_data(i).offset] ); sqlsrv_free( out_of_row_data ); } } @@ -1525,7 +1525,7 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in sqlsrv_malloc_auto_ptr buffer; buffer = static_cast( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN ))); SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'}; SQLLEN last_field_len = 0; bool full_length_returned = false; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 5530a554..6c49464c 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -70,7 +70,7 @@ typedef struct _OSVERSIONINFOA { DWORD dwMinorVersion; DWORD dwBuildNumber; DWORD dwPlatformId; - CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage + CHAR szCSDVersion[128]; // Maintenance string for PSS usage } OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA; typedef OSVERSIONINFOA OSVERSIONINFO; #endif // !_WIN32 @@ -175,7 +175,7 @@ const int SQL_SERVER_MAX_TYPE_SIZE = 0; const int SQL_SERVER_MAX_PARAMS = 2100; // increase the maximum message length to accommodate for the long error returned for operand type clash // or for conversion of a long string -const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 8; +const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2; // max size of a date time string when converting from a DateTime object to a string const int MAX_DATETIME_STRING_LEN = 256; @@ -546,21 +546,21 @@ public: // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ int index ) const { - return _ptr[ index ]; + return _ptr[index]; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ unsigned int index ) const { - return _ptr[ index ]; + return _ptr[index]; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ long index ) const { - return _ptr[ index ]; + return _ptr[index]; } @@ -577,7 +577,7 @@ public: // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ unsigned short index ) const { - return _ptr[ index ]; + return _ptr[index]; } // access elements of a structure through the auto ptr @@ -693,9 +693,9 @@ public: // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. void reset( _In_opt_ HashTable* ptr = NULL ) { - if( _ptr ) { - zend_hash_destroy( _ptr ); - FREE_HASHTABLE( _ptr ); + if (_ptr != NULL) { + zend_hash_destroy(_ptr); + FREE_HASHTABLE(_ptr); } _ptr = ptr; } @@ -1076,6 +1076,8 @@ struct sqlsrv_conn : public sqlsrv_context { col_encryption_option ce_option; // holds the details of what are required to enable column encryption DRIVER_VERSION driver_version; // version of ODBC driver + sqlsrv_malloc_auto_ptr azure_ad_access_token; + // initialize with default values sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) @@ -1096,6 +1098,7 @@ enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, SQLSRV_STMT_OPTION_SCROLLABLE, SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + SQLSRV_STMT_OPTION_DATE_AS_STRING, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, @@ -1105,6 +1108,7 @@ enum SQLSRV_STMT_OPTIONS { namespace ODBCConnOptions { const char APP[] = "APP"; +const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; @@ -1140,6 +1144,7 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_INVALID, SQLSRV_CONN_OPTION_APP, + SQLSRV_CONN_OPTION_ACCESS_TOKEN, SQLSRV_CONN_OPTION_CHARACTERSET, SQLSRV_CONN_OPTION_CONN_POOLING, SQLSRV_CONN_OPTION_DATABASE, @@ -1222,14 +1227,14 @@ struct driver_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; -struct ce_ksp_provider_set_func { - static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); -}; - struct ce_akv_str_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; +struct access_token_set_func { + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); +}; + // factory to create a connection (since they are subclassed to instantiate statements) typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); @@ -1278,6 +1283,11 @@ struct stmt_option_buffered_query_limit : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; +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 ); +}; + // used to hold the table for statment options struct stmt_option { @@ -1389,6 +1399,7 @@ struct sqlsrv_stmt : public sqlsrv_context { // last results 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 // 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 @@ -1718,6 +1729,8 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_AKV_SECRET_MISSING, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED, + SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, + SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, @@ -1889,14 +1902,20 @@ namespace core { // and return a more helpful message prepended to the ODBC errors if that error occurs if( !SQL_SUCCEEDED( r )) { - SQLCHAR err_msg[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { '\0' }; + SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, - err_msg, SQL_MAX_ERROR_MESSAGE_LENGTH, &len ); + err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); + if (rtemp == SQL_SUCCESS_WITH_INFO && len > SQL_MAX_MESSAGE_LENGTH) { + // if the error message is this long, then it must not be the mars message + // defined as ODBC_CONNECTION_BUSY_ERROR -- so return here and continue the + // regular error handling + return; + } CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) { - + throw CoreException(); } @@ -2377,10 +2396,13 @@ namespace core { inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { - int zr = ::array_init(new_array); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { +#if PHP_VERSION_ID < 70300 + CHECK_ZEND_ERROR(::array_init(new_array), ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } +#else + array_init(new_array); +#endif } inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z TSRMLS_DC ) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 6d4d4f61..46d60907 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -36,7 +36,8 @@ struct field_cache { : type( t ) { // if the value is NULL, then just record a NULL pointer - if( field_value != NULL ) { + // field_len may be equal to SQL_NULL_DATA even when field_value is not null + if( field_value != NULL && field_len != SQL_NULL_DATA) { value = sqlsrv_malloc( field_len ); memcpy_s( value, field_len, field_value, field_len ); len = field_len; @@ -140,6 +141,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error last_field_index( -1 ), past_next_result_end( false ), query_timeout( QUERY_TIMEOUT_INVALID ), + date_as_string(false), 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 send_streams_at_exec( true ), @@ -364,7 +366,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ if( stmt->param_ind_ptrs.size() < static_cast( param_num + 1 )){ stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA ); } - SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ]; + SQLLEN& ind_ptr = stmt->param_ind_ptrs[param_num]; zval* param_ref = param_z; if( Z_ISREF_P( param_z )){ @@ -964,7 +966,7 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { // prevent the 'string not null terminated' warning - reinterpret_cast( field_value )[ cached->len ] = '\0'; + reinterpret_cast( field_value )[cached->len] = '\0'; } *field_len = cached->len; if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } @@ -1236,7 +1238,7 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ]; + char lock_timeout_sql[32] = {'\0'}; int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), @@ -1304,7 +1306,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } // read the data from the stream, send it via SQLPutData and track how much we've sent. else { - char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + char buffer[PHP_STREAM_BUFFER_SIZE + 1] = {'\0'}; std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); @@ -1325,7 +1327,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a // expansion of 2x the UTF-8 size. - SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = {L'\0'}; int wbuffer_size = static_cast( sizeof( wbuffer ) / sizeof( SQLWCHAR )); DWORD last_error_code = ERROR_SUCCESS; // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate @@ -1403,6 +1405,15 @@ void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, s core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); } +void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +{ + if (zend_is_true(value_z)) { + stmt->date_as_string = true; + } + else { + stmt->date_as_string = false; + } +} // 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. @@ -1631,7 +1642,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // convert it to a DateTime object and return the created object case SQLSRV_PHPTYPE_DATETIME: { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + char field_value_temp[MAX_DATETIME_STRING_LEN] = {'\0'}; zval params[1]; zval field_value_temp_z; zval function_z; @@ -1823,7 +1834,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve } // null terminate the string, set the size within the zval, and return success - wbuffer[ wchar_size ] = L'\0'; + wbuffer[wchar_size] = L'\0'; core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), wchar_size * sizeof( SQLWCHAR ) ); sqlsrv_free(wbuffer); wbuffer.transferred(); @@ -2075,7 +2086,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter char* str = Z_STRVAL_P( value_z ); - SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; + SQLLEN str_len = stmt->param_ind_ptrs[output_param->param_num]; if( str_len == 0 ) { core::sqlsrv_zval_stringl( value_z, "", 0 ); continue; @@ -2127,7 +2138,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated // so we do that here if the length of the returned data is less than the original allocation. The // original allocation null terminates the buffer already. - str[ str_len ] = '\0'; + str[str_len] = '\0'; core::sqlsrv_zval_stringl(value_z, str, str_len); } else { @@ -2137,7 +2148,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) break; case IS_LONG: // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { + if( stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA ) { ZVAL_NULL( value_z ); } else if( output_param->is_bool ) { @@ -2258,7 +2269,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind if( r == SQL_SUCCESS_WITH_INFO ) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { 0 }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; SQLSMALLINT len = 0; stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); @@ -2442,11 +2453,11 @@ field_value = field_value_temp; stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC ) { - for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { + for( int i = 0; stmt_opts[i].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { // if we find the key we're looking for, return it - if( key == stmt_opts[ i ].key ) { - return &stmt_opts[ i ]; + if( key == stmt_opts[i].key ) { + return &stmt_opts[i]; } } @@ -2580,8 +2591,8 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which // may be less than the size of the buffer since the output may be more than the input. If it is greater, // than the error 22001 is returned by ODBC. - if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) { - stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra); + if( stmt->param_ind_ptrs[paramno] > buffer_len - (elem_size - buffer_null_extra)) { + stmt->param_ind_ptrs[paramno] = buffer_len - (elem_size - buffer_null_extra); } } diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 32780a5b..e4e9e485 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -106,7 +106,7 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) // if it's not a binary encoded field if( r == SQL_SUCCESS_WITH_INFO ) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { 0 }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; SQLSMALLINT len = 0; ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index d8b7b244..ca097a24 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -28,7 +28,7 @@ log_callback g_driver_log; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; // buffer used to hold a formatted log message prior to actually logging it. -char last_err_msg[ 2048 ]; // 2k to hold the error messages +char last_err_msg[2048] = {'\0'}; // 2k to hold the error messages // routine used by utf16_string_from_mbcs_string unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, @@ -219,8 +219,8 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu SQLRETURN r = SQL_SUCCESS; SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { L'\0' }; + SQLWCHAR wsqlstate[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; + SQLWCHAR wnative_message[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {L'\0'}; SQLSRV_ENCODING enc = ctx.encoding(); switch( h_type ) { @@ -265,10 +265,36 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu // We need to calculate number of characters SQLINTEGER wsqlstate_len = sizeof( wsqlstate ) / sizeof( SQLWCHAR ); SQLLEN sqlstate_len = 0; - convert_string_from_utf16(enc, wsqlstate, wsqlstate_len, (char**)&error->sqlstate, sqlstate_len); + convert_string_from_utf16(enc, wsqlstate, wsqlstate_len, (char**)&error->sqlstate, sqlstate_len); + SQLLEN message_len = 0; - convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); + if (r == SQL_SUCCESS_WITH_INFO && wmessage_len > SQL_MAX_ERROR_MESSAGE_LENGTH) { + // note that wmessage_len is the number of characters required for the error message -- + // create a new buffer big enough for this lengthy error message + sqlsrv_malloc_auto_ptr wnative_message_str; + + SQLSMALLINT expected_len = wmessage_len * sizeof(SQLWCHAR); + SQLSMALLINT returned_len = 0; + + wnative_message_str = reinterpret_cast(sqlsrv_malloc(expected_len)); + memset(wnative_message_str, '\0', expected_len); + + SQLRETURN rtemp = ::SQLGetDiagFieldW(h_type, h, record_number, SQL_DIAG_MESSAGE_TEXT, wnative_message_str, wmessage_len, &returned_len); + if (!SQL_SUCCEEDED(rtemp) || returned_len != expected_len) { + // something went wrong + return false; + } + + convert_string_from_utf16(enc, wnative_message_str, wmessage_len, (char**)&error->native_message, message_len); + } else { + convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); + } + + if (message_len == 0 && error->native_message == NULL) { + // something went wrong + return false; + } break; } @@ -352,11 +378,11 @@ void die( _In_opt_ const char* msg, ... ) va_start( format_args, msg ); DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args ); va_end( format_args ); - if( rc == 0 ) { - php_error( E_ERROR, reinterpret_cast( INTERNAL_FORMAT_ERROR )); + if (rc == 0) { + php_error(E_ERROR, "%s", reinterpret_cast(INTERNAL_FORMAT_ERROR)); } - php_error( E_ERROR, last_err_msg ); + php_error(E_ERROR, "%s", last_err_msg); } namespace { @@ -393,7 +419,7 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin if( required_len == 0 ) { return 0; } - utf16_out_string[ required_len ] = '\0'; + utf16_out_string[required_len] = '\0'; return required_len; } diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 88e8d1a4..98619d61 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -362,7 +362,7 @@ public: { // Use fixed size buffer iteratively to determine final required length const size_t CCH_FIXED_SIZE = 256; - char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ]; + char fixed_buf[CCH_FIXED_SIZE*sizeof(DestType)] = {'\0'}; iconv_buffer dest( &fixed_buf[0], CCH_FIXED_SIZE ); diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index cc0163f0..b8c04643 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 e8c8e5bb..6977ff22 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 bf2bc9ca..6aa43fb0 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 2ec13e09..79bd860e 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 75251eb6..669462ab 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 3a759252..f4912b11 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -92,6 +92,7 @@ #define SQL_COPT_SS_TRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13)// List of trusted CMK paths #define SQL_COPT_SS_CEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL #define SQL_COPT_SS_AUTHENTICATION (SQL_COPT_SS_BASE_EX+15)// The authentication method used for the connection +#define SQL_COPT_SS_ACCESS_TOKEN (SQL_COPT_SS_BASE_EX+16)// The authentication access token used for the connection // SQLColAttributes driver specific defines. // SQLSetDescField/SQLGetDescField driver specific defines. @@ -370,6 +371,12 @@ #pragma warning(disable:4200) #endif +typedef struct AccessToken +{ + unsigned int dataSize; + char data[]; +} ACCESSTOKEN; + // Keystore Provider interface definition typedef struct CEKeystoreContext { diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 78478eaa..8d5b785c 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 dc3b4ca5..574dc51a 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 7d655443..0ad9dcd1 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 3 +#define SQLVERSION_MINOR 4 #define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 0 +#define PREVIEW 1 #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 baa393e2..8e113a5c 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 1baa473a..03706dd3 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 44eb7c84..a5bdf641 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 36aceae1..27426310 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 6b419066..c0e4d3af 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Microsoft Drivers 5.4 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 449789c4..b6c2b1b1 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -35,6 +35,9 @@ if( PHP_SQLSRV != "no" ) { ADD_FLAG( "CFLAGS_SQLSRV", "/EHsc" ); ADD_FLAG( "CFLAGS_SQLSRV", "/GS" ); ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" ); + if (VCVERS >= 1913) { + ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre"); + } if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" ); EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 7bf80508..dcfc755e 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -173,6 +173,7 @@ namespace SSStmtOptionNames { const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec"; const char SCROLLABLE[] = "Scrollable"; const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; + const char DATE_AS_STRING[] = "ReturnDatesAsStrings"; } namespace SSConnOptionNames { @@ -180,6 +181,7 @@ namespace SSConnOptionNames { // most of these strings are the same for both the sqlsrv_connect connection option // and the name put into the connection string. MARS is the only one that's different. const char APP[] = "APP"; +const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; @@ -242,6 +244,12 @@ const stmt_option SS_STMT_OPTS[] = { SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, + { + SSStmtOptionNames::DATE_AS_STRING, + sizeof( SSStmtOptionNames::DATE_AS_STRING ), + SQLSRV_STMT_OPTION_DATE_AS_STRING, + std::unique_ptr( new stmt_option_date_as_string ) + }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -257,6 +265,15 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, + { + SSConnOptionNames::AccessToken, + sizeof( SSConnOptionNames::AccessToken ), + SQLSRV_CONN_OPTION_ACCESS_TOKEN, + ODBCConnOptions::AccessToken, + sizeof( ODBCConnOptions::AccessToken), + CONN_ATTR_STRING, + access_token_set_func::func + }, { SSConnOptionNames::ApplicationIntent, sizeof( SSConnOptionNames::ApplicationIntent ), @@ -978,7 +995,7 @@ PHP_FUNCTION( sqlsrv_prepare ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); @@ -1101,7 +1118,7 @@ PHP_FUNCTION( sqlsrv_query ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); @@ -1234,12 +1251,12 @@ void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC ) int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z TSRMLS_DC ) { - for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + for( int i=0; SS_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) { - if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[ i ].sqlsrv_name )) { + if( key_len == SS_CONN_OPTS[i].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[i].sqlsrv_name )) { - switch( SS_CONN_OPTS[ i ].value_type ) { + switch( SS_CONN_OPTS[i].value_type ) { case CONN_ATTR_BOOL: // bool attributes can be either strings to be appended to the connection string @@ -1250,7 +1267,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In case CONN_ATTR_INT: { CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SS_CONN_OPTS[ i ].sqlsrv_name ) + SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1259,7 +1276,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In case CONN_ATTR_STRING: { CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SS_CONN_OPTS[ i ].sqlsrv_name ) { + SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1268,7 +1285,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In size_t value_len = Z_STRLEN_P( value_z ); bool escaped = core_is_conn_opt_value_escaped( value, value_len ); - CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { + CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1278,7 +1295,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In valid = core_is_authentication_option_valid( value, value_len ); } - CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[ i ].sqlsrv_name ) { + CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1287,7 +1304,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In } } - return SS_CONN_OPTS[ i ].conn_option_key; + return SS_CONN_OPTS[i].conn_option_key; } } return SQLSRV_CONN_OPTION_INVALID; @@ -1295,10 +1312,10 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC ) { - for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) + for( int i = 0; SS_STMT_OPTS[i].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { - if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { - return SS_STMT_OPTS[ i ].key; + if( key_len == SS_STMT_OPTS[i].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[i].name )) { + return SS_STMT_OPTS[i].key; } } return SQLSRV_STMT_OPTION_INVALID; diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index bb8ab04b..45dfb0e5 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -537,9 +537,9 @@ PHP_MINIT_FUNCTION(sqlsrv) g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - for( int i = 0; SS_ERRORS[ i ].error_code != UINT_MAX; ++i ) { - if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[ i ].error_code, - &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ))) { + for( int i = 0; SS_ERRORS[i].error_code != UINT_MAX; ++i ) { + if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[i].error_code, + &( SS_ERRORS[i].sqlsrv_error ), sizeof( SS_ERRORS[i].sqlsrv_error ))) { LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ ); return FAILURE; } diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 6c5e7b01..1816942a 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 ac334cb9..3bbd4af6 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -129,6 +129,10 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ fetch_fields_count ( 0 ) { core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); + + // initialize date_as_string based on the corresponding connection option + ss_sqlsrv_conn* ss_conn = static_cast(conn); + date_as_string = ss_conn->date_as_string; } ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) @@ -137,7 +141,7 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) for( int i=0; i < fetch_fields_count; ++i ) { - sqlsrv_free( fetch_field_names[ i ].name ); + sqlsrv_free( fetch_field_names[i].name ); } sqlsrv_free( fetch_field_names ); } @@ -155,7 +159,7 @@ void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) for( int i=0; i < fetch_fields_count; ++i ) { - sqlsrv_free( fetch_field_names[ i ].name ); + sqlsrv_free( fetch_field_names[i].name ); } sqlsrv_free( fetch_field_names ); } @@ -230,7 +234,7 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _ case SQL_SS_TIMESTAMPOFFSET: case SQL_SS_TIME2: case SQL_TYPE_TIMESTAMP: - if( reinterpret_cast( this->conn )->date_as_string ) { + if (this->date_as_string) { ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; ss_phptype.typeinfo.encoding = this->conn->encoding(); } @@ -719,7 +723,7 @@ PHP_FUNCTION( sqlsrv_num_fields ) } } -// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]]) +// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams]]) // // Retrieves the next row of data as a PHP object. // @@ -889,7 +893,9 @@ PHP_FUNCTION( sqlsrv_fetch_object ) fci.object = Z_OBJ_P( &retval_z ); memset( &fcic, 0, sizeof( fcic )); +#if PHP_VERSION_ID < 70300 fcic.initialized = 1; +#endif fcic.function_handler = class_entry->constructor; fcic.calling_scope = class_entry; @@ -1676,8 +1682,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ case SQL_SS_TIME2: case SQL_TYPE_TIMESTAMP: { - ss_sqlsrv_conn* c = static_cast( stmt->conn ); - if( c->date_as_string ) { + if (stmt->date_as_string) { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); } @@ -1776,7 +1781,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ SQLLEN field_name_len = 0; SQLSMALLINT field_name_len_w = 0; - SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2 ] = { L'\0' }; + SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2] = {L'\0'}; sqlsrv_malloc_auto_ptr field_name; sqlsrv_malloc_auto_ptr field_names; field_names = static_cast( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); @@ -1806,10 +1811,14 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ field_names.transferred(); } - int zr = array_init( &fields ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } + int zr = SUCCESS; +#if PHP_VERSION_ID < 70300 + CHECK_ZEND_ERROR(array_init(&fields), stmt, SQLSRV_ERROR_ZEND_HASH) { + throw ss::SSException(); + } +#else + array_init(&fields); +#endif for( int i = 0; i < num_cols; ++i ) { SQLLEN field_len = -1; @@ -1836,7 +1845,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ throw ss::SSException(); } - if( stmt->fetch_field_names[ i ].len > 1 || allow_empty_field_names ) { + if( stmt->fetch_field_names[i].len > 1 || allow_empty_field_names ) { zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index ffee8537..d70a93a9 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 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 bc26658c..aab5e2eb 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.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -28,7 +28,7 @@ unsigned int current_log_subsystem = LOG_UTIL; // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ]; +char log_msg[LOG_MSG_SIZE] = {'\0'}; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; @@ -420,6 +420,14 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, { IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -114, false} }, + { + SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -115, false} + }, + { + SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false} + }, // terminate the list of errors/warnings { UINT_MAX, {} } @@ -498,18 +506,21 @@ PHP_FUNCTION( sqlsrv_errors ) LOG_FUNCTION( "sqlsrv_errors" ); - if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || - ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { - LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); - RETURN_FALSE; - } - int result; - zval err_z; - ZVAL_UNDEF( &err_z ); - result = array_init( &err_z ); - if( result == FAILURE ) { - RETURN_FALSE; - } + if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || + ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { + LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); + RETURN_FALSE; + } + zval err_z; + ZVAL_UNDEF(&err_z); +#if PHP_VERSION_ID < 70300 + if (array_init(&err_z) == FAILURE) { + RETURN_FALSE; + } +#else + array_init(&err_z); +#endif + if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) { if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) TSRMLS_CC )) { zval_ptr_dtor(&err_z); @@ -746,10 +757,13 @@ sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code ) { void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ bool warning TSRMLS_DC ) { - - if( array_init( error_z ) == FAILURE ) { +#if PHP_VERSION_ID < 70300 + if (array_init(error_z) == FAILURE) { DIE( "Fatal error during error processing" ); } +#else + array_init(error_z); +#endif // sqlstate zval temp; @@ -828,7 +842,6 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo size_t prev_reported_cnt = 0; bool reported_chain_was_null = false; bool ignored_chain_was_null = false; - int zr = SUCCESS; zval error_z; ZVAL_UNDEF(&error_z); sqlsrv_error_auto_ptr error; @@ -837,10 +850,13 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( Z_TYPE_P( reported_chain ) == IS_NULL ) { reported_chain_was_null = true; - zr = array_init( reported_chain ); - if( zr == FAILURE ) { - DIE( "Fatal error in handle_errors_and_warnings" ); +#if PHP_VERSION_ID < 70300 + if (array_init(reported_chain) == FAILURE) { + DIE( "Fatal error during error processing" ); } +#else + array_init(reported_chain); +#endif } else { prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); @@ -851,11 +867,14 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { - ignored_chain_was_null = true; - zr = array_init( ignored_chain ); - if( zr == FAILURE ) { + ignored_chain_was_null = true; +#if PHP_VERSION_ID < 70300 + if (array_init(ignored_chain) == FAILURE) { DIE( "Fatal error in handle_errors_and_warnings" ); } +#else + array_init( ignored_chain ); +#endif } } diff --git a/test/functional/pdo_sqlsrv/access_token.inc b/test/functional/pdo_sqlsrv/access_token.inc new file mode 100644 index 00000000..dbb2f778 --- /dev/null +++ b/test/functional/pdo_sqlsrv/access_token.inc @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt new file mode 100644 index 00000000..f468ffd6 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt @@ -0,0 +1,157 @@ +--TEST-- +Test some basics of Azure AD Access Token support +--DESCRIPTION-- +This test also expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- +getMessage(), $expectedError) === false) { + echo "AzureAD access token test: expected to fail with $msg\n"; + + print_r($exception->getMessage()); + echo "\n"; + } +} + +function connectWithEmptyAccessToken($server) +{ + $dummyToken = ''; + $expectedError = 'The Azure AD Access Token is empty. Expected a byte string.'; + + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'empty token'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo"); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); +} + +function connectWithInvalidOptions($server) +{ + $dummyToken = 'abcde'; + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + $message = 'AzureAD access token test: expected to fail with '; + + $uid = ''; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'empty UID provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = ''; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'empty PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $uid = 'uid'; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'UID provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = ''; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $connectionInfo = "Authentication = SqlPassword; AccessToken = $dummyToken;"; + $testCase = 'Authentication keyword'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo"); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); +} + +function simpleTest($conn) +{ + // Create table + $tableName = 'Simple'; + $col1 = 'Some simple string value'; + + dropTable($conn, $tableName); + + $query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(25))"; + $stmt = $conn->query($query); + + // Insert one row + $query = "INSERT INTO $tableName VALUES ('$col1')"; + $stmt = $conn->query($query); + + // Fetch data + $query = "SELECT * FROM $tableName"; + $stmt = $conn->query($query); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $id = $result[0]; + if ($id != 1) { + echo "AzureAD access token test: fetched id $id unexpected\n"; + } + + $field = $result[1]; + if ($field !== $col1) { + echo "AzureAD access token test: fetched value $field unexpected\n"; + } + + dropTable($conn, $tableName); +} + +// First test some error conditions +require_once('MsSetup.inc'); +connectWithInvalidOptions($server); + +// Then, test with an empty access token +connectWithEmptyAccessToken($server); + +// Next, test with a valid access token and perform some simple tasks +require_once('access_token.inc'); +try { + if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { + $connectionInfo = "Database = $adDatabase; AccessToken = $accToken;"; + $conn = new PDO("sqlsrv:server = $adServer; $connectionInfo"); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + simpleTest($conn); + unset($conn); + } +} catch(PDOException $e) { + print_r( $e->getMessage() ); + echo PHP_EOL; +} + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt new file mode 100644 index 00000000..9cc1bc3b --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt @@ -0,0 +1,87 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE and datetimes as output params +--DESCRIPTION-- +Do not support returning DateTime objects as output parameters. Setting attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE to true should have no effect. +--SKIPIF-- + +--FILE-- + false); + $conn = connect("", $attr); + + // Generate input values for the test table + $query = 'SELECT SYSDATETIME(), SYSDATETIMEOFFSET(), CONVERT(time, CURRENT_TIMESTAMP)'; + $stmt = $conn->query($query); + $values = $stmt->fetch(PDO::FETCH_NUM); + + // create a test table with the above input date time values + $tableName = "TestDateTimeOutParam"; + $columns = array('c1', 'c2', 'c3'); + $dataTypes = array("datetime2", "datetimeoffset", "time"); + + $colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]), + new ColumnMeta($dataTypes[1], $columns[1]), + new ColumnMeta($dataTypes[2], $columns[2])); + createTable($conn, $tableName, $colMeta); + + $query = "INSERT INTO $tableName VALUES(?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB); + } + $stmt->execute(); + + $lobException = 'An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.'; + + for ($i = 0; $i < count($columns); $i++) { + // create the stored procedure first + $storedProcName = "spDateTimeOutParam" . $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 type PDO::PARAM_STR + $dateStr = ''; + $outSql = getCallProcSqlPlaceholders($storedProcName, 1); + $options = array(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE => true); + $stmt = $conn->prepare($outSql, $options); + $stmt->bindParam(1, $dateStr, PDO::PARAM_STR, 1024); + $stmt->execute(); + + if ($dateStr != $values[$i]) { + echo "Expected $values[$i] for column ' . ($i+1) .' but got: "; + var_dump($dateStr); + } + + // for output param type PDO::PARAM_LOB it should fail with the correct exception + try { + $stmt->bindParam(1, $dateStr, PDO::PARAM_LOB, 1024); + $stmt->execute(); + echo "Expected this to fail\n"; + } catch (PDOException $e) { + $message = $e->getMessage(); + $matched = strpos($message, $lobException); + if (!$matched) { + var_dump($e->errorInfo); + } + } + + dropProc($conn, $storedProcName); + } + + dropTable($conn, $tableName); + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt new file mode 100644 index 00000000..ab8ba5ec --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt @@ -0,0 +1,238 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for date, time and datetime columns +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for datetime, datetime2, +smalldatetime, datetimeoffset and time columns. The input values are based on current +timestamp and they are retrieved either as strings or date time objects. Note that the +existing attributes ATTR_STRINGIFY_FETCHES and SQLSRV_ATTR_FETCHES_NUMERIC_TYPE +should have no effect on data retrieval. +--SKIPIF-- + +--FILE-- +format('Y-m-d H:i:s.u'); + + // actual datetime value from date time object to string + $dtActual = date_format($dtObj, 'Y-m-d H:i:s.u'); + if ($dtActual != $dtExpected) { + echo "Expected $dtExpected for column $column but the actual value was $dtActual\n"; + } +} + +function runTest($conn, $query, $columns, $values, $useBuffer = false) +{ + // fetch the date time values as strings or date time objects + // prepare with or without buffered cursor + $options = array(); + if ($useBuffer) { + $options = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED); + } + + // fetch_numeric off, fetch_datetime off + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $obj = $stmt->fetch(PDO::FETCH_OBJ); + checkStringValues($obj, $columns, $values); + + // fetch_numeric off, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_ASSOC); + + // fetch_numeric on, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH); + + // ATTR_STRINGIFY_FETCHES should have no effect when fetching date time objects + // Setting it to true only converts numeric values to strings when fetching + // See http://www.php.net/manual/en/pdo.setattribute.php for details + // stringify on, fetch_numeric off, fetch_datetime on + $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + checkColumnDTValue($i, $columns[$i], $values, $dtObj); + } while (++$i < count($columns)); + + // reset stringify to off + // fetch_numeric off, fetch_datetime off + $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_OBJ); + checkStringValues($obj, $columns, $values); + + // conn attribute fetch_datetime on, but statement attribute fetch_datetime off -- + // expected strings to be returned because statement attribute overrides the + // connection attribute + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $obj = $stmt->fetch(PDO::FETCH_OBJ); + checkStringValues($obj, $columns, $values); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime on -- + // expected datetime objects to be returned (this time no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_ASSOC); + + // likewise, conn attribute fetch_datetime off, but statement attribute + // fetch_datetime on -- expected datetime objects to be returned + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime off -- + // expected strings to be returned (again no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $obj = $stmt->fetch(PDO::FETCH_LAZY); + checkStringValues($obj, $columns, $values); + + // last test: set statement attribute fetch_datetime on with no change to + // prepared statement -- expected datetime objects to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + checkColumnDTValue($i, $columns[$i], $values, $dtObj); + } while (++$i < count($columns)); + + // keep the same settings but test with FETCH_BOUND + for ($i = 0; $i < count($columns); $i++) { + $dateObj = null; + $stmt->execute(); + $stmt->bindColumn($i + 1, $dateObj, PDO::PARAM_LOB); + $row = $stmt->fetch(PDO::FETCH_BOUND); + checkColumnDTValue($i, $columns[$i], $values, $dateObj); + } + + // redo the test but with fetch_datetime off + // expected strings to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + for ($i = 0; $i < count($columns); $i++) { + $dateStr = null; + $stmt->execute(); + $stmt->bindColumn($i + 1, $dateStr); + $row = $stmt->fetch(PDO::FETCH_BOUND); + if ($dateStr != $values[$i]) { + $col = $columns[$i]; + echo "Expected $values[$i] for column $col but the bound value was: "; + var_dump($dateStr); + } + } +} + +try { + date_default_timezone_set('America/Los_Angeles'); + + $conn = connect(); + + // Generate input values for the test table + $query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), + CONVERT(smalldatetime, SYSDATETIME()), + CONVERT(datetime, SYSDATETIME()), + SYSDATETIMEOFFSET(), + CONVERT(time, SYSDATETIME())'; + + $stmt = $conn->query($query); + $values = $stmt->fetch(PDO::FETCH_NUM); + + // create a test table with the above input date time values + $tableName = "TestDateTimeOffset"; + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + $dataTypes = array('date', 'datetime2', 'smalldatetime', 'datetime', 'datetimeoffset', 'time'); + + $colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]), + new ColumnMeta($dataTypes[1], $columns[1]), + new ColumnMeta($dataTypes[2], $columns[2]), + new ColumnMeta($dataTypes[3], $columns[3]), + new ColumnMeta($dataTypes[4], $columns[4]), + new ColumnMeta($dataTypes[5], $columns[5])); + createTable($conn, $tableName, $colMeta); + + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB); + } + $stmt->execute(); + + $query = "SELECT * FROM $tableName"; + + runTest($conn, $query, $columns, $values); + runTest($conn, $query, $columns, $values, true); + + dropTable($conn, $tableName); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt new file mode 100644 index 00000000..9bffdf75 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt @@ -0,0 +1,163 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for datetime types with null values +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for different datetime types with +null values. Whether retrieved as strings or date time objects should return NULLs. +--SKIPIF-- + +--FILE-- + PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED); + } + + // fetch_numeric off, fetch_datetime off + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNullStrings($row, $columns); + + // fetch_numeric off, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkNullDTObjects($row, $columns, PDO::FETCH_ASSOC); + + // fetch_numeric on, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkNullDTObjects($row, $columns, PDO::FETCH_BOTH); + + // conn attribute fetch_datetime on, but statement attribute fetch_datetime off -- + // expected strings to be returned because statement attribute overrides the + // connection attribute + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNullStrings($row, $columns); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime on -- + // expected datetime objects to be returned (this time no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkNullDTObjects($row, $columns, PDO::FETCH_ASSOC); + + // likewise, conn attribute fetch_datetime off, but statement attribute + // fetch_datetime on -- expected datetime objects to be returned + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkNullDTObjects($row, $columns, PDO::FETCH_BOTH); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime off -- + // expected strings to be returned (again no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNullStrings($row, $columns); + + // last test: set statement attribute fetch_datetime on with no change to + // prepared statement -- expected datetime objects to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + if (!is_null($dtObj)) { + echo "Expected NULL for column " . ($i + 1) . " but got: "; + var_dump($dtObj); + } + } while (++$i < count($columns)); +} + +try { + $conn = connect(); + + // create a test table + $tableName = "TestNullDateTime"; + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + $colMeta = array(new ColumnMeta('date', $columns[0]), + new ColumnMeta('datetime', $columns[1]), + new ColumnMeta('smalldatetime', $columns[2]), + new ColumnMeta('datetime2', $columns[3]), + new ColumnMeta('datetimeoffset', $columns[4]), + new ColumnMeta('time', $columns[5])); + createTable($conn, $tableName, $colMeta); + + $value = null; + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $value, PDO::PARAM_NULL); + } + $stmt->execute(); + + $query = "SELECT * FROM $tableName"; + + runTest($conn, $query, $columns); + runTest($conn, $query, $columns, true); + + dropTable($conn, $tableName); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc b/test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc new file mode 100644 index 00000000..d59c02bf --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc @@ -0,0 +1,41 @@ +getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; +$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0]; + +$isWin = (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN'); +if (!$isWin && $msodbcsqlMaj < 17) { + die("skip: Unsupported ODBC driver version"); +} + +// Now check 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"); + } +} + +?> diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index 10849b9b..39d92e39 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -7,13 +7,13 @@ if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd ); +$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); if ($conn === false) { - die( "skip Could not connect during SKIPIF." ); + die("skip Could not connect during SKIPIF."); } $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; @@ -21,19 +21,23 @@ $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; if (!$is_win) { - if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { + if ($msodbcsql_maj < 17) { die("skip Unsupported ODBC driver version"); + } else { + if ($msodbcsql_maj==17 && $msodbcsql_min < 2) { + die("skip Unsupported ODBC driver version"); + } } } // Get SQL Server Version // Exclude this check if running on Azure if (!$daasMode) { - $stmt = $conn->query( "SELECT @@VERSION" ); + $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."); + die("skip Could not fetch SQL Server version during SKIPIF."); } $version = explode(' ', $ver_string); 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 72553974..37656773 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc @@ -7,11 +7,11 @@ if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd ); +$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); if ($conn === false) { die( "skip Could not connect during SKIPIF." ); } diff --git a/test/functional/sqlsrv/access_token.inc b/test/functional/sqlsrv/access_token.inc new file mode 100644 index 00000000..dbb2f778 --- /dev/null +++ b/test/functional/sqlsrv/access_token.inc @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_azure_ad_acess_token.inc b/test/functional/sqlsrv/skipif_azure_ad_acess_token.inc new file mode 100644 index 00000000..3c7c447c --- /dev/null +++ b/test/functional/sqlsrv/skipif_azure_ad_acess_token.inc @@ -0,0 +1,45 @@ +$userName, "PWD"=>$userPassword, "Driver" => $driver); + +$conn = sqlsrv_connect($server, $connectionInfo); +if ($conn === false) { + die("skip: Could not connect during SKIPIF."); +} + +$msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer']; +$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0]; + +$isWin = (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN'); + +if (!$isWin && $msodbcsqlMaj < 17) { + die("skip: Unsupported ODBC driver version"); +} + +// Now check SQL Server version - exclude this check if running on Azure +if (!$daasMode) { + // Get SQL Server version + $stmt = sqlsrv_query($conn, "SELECT @@VERSION"); + if (sqlsrv_fetch($stmt)) { + $verString = sqlsrv_get_field($stmt, 0); + } else { + die("skip Could not fetch SQL Server version."); + } + + $version = explode(' ', $verString); + + if ($version[3] < '2016') { + die("skip: Wrong version of SQL Server, 2016 or later required"); + } +} + +?> diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index 74d041ff..e8c53f2d 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -7,15 +7,15 @@ if (!extension_loaded("sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword ); +$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); -$conn = sqlsrv_connect( $server, $connectionInfo ); +$conn = sqlsrv_connect($server, $connectionInfo); if ($conn === false) { - die( "skip Could not connect during SKIPIF." ); + die("skip Could not connect during SKIPIF."); } $msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; @@ -23,17 +23,21 @@ $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; if (!$is_win) { - if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { + if ($msodbcsql_maj < 17) { die("skip Unsupported ODBC driver version"); + } else { + if ($msodbcsql_maj==17 && $msodbcsql_min < 2) { + die("skip Unsupported ODBC driver version"); + } } } // Get SQL Server version // Exclude this check if running on Azure if (!$daasMode) { - $stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); + $stmt = sqlsrv_query($conn, "SELECT @@VERSION"); if (sqlsrv_fetch($stmt)) { - $ver_string = sqlsrv_get_field( $stmt, 0 ); + $ver_string = sqlsrv_get_field($stmt, 0); } else { die("skip Could not fetch SQL Server version."); } diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc index ce06258a..303a2030 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc @@ -7,11 +7,11 @@ if (!extension_loaded("sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword ); +$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); $conn = sqlsrv_connect( $server, $connectionInfo ); if ($conn === false) { diff --git a/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt b/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt index ba48e9f6..d4ea1c94 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt @@ -29,8 +29,10 @@ function checkErrors(&$convError) $convError[0][1] != '8114' and $convError[0][1] != '8169') { print_r($convError); - fatalError("Conversion failed with unexpected error message. i=$i, j=$j, v=$v\n"); - } + return false; + } + + return true; } // Build the select queries. We want every combination of types for conversion @@ -202,7 +204,9 @@ for ($v = 0; $v < sizeof($values); ++$v) { if ($stmt == false) { $convError = sqlsrv_errors(); - checkErrors($convError); + if (!checkErrors($convError)) { + fatalError("Conversion failed with unexpected error message. i=$i, j=$j, v=$v\n"); + } if (AE\isDataEncrypted()) { $stmtAE = sqlsrv_query($conn, $selectQueryAE[$i][$j]); diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt new file mode 100644 index 00000000..96c82d63 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt @@ -0,0 +1,131 @@ +--TEST-- +Test some basics of Azure AD Access Token support +--DESCRIPTION-- +This test also expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- + "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty token'); + unset($connectionInfo); +} + +function connectWithInvalidOptions($server) +{ + $dummyToken = 'abcde'; + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + + $connectionInfo = array("UID"=>"", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty UID provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty PWD provided'); + unset($connectionInfo); + + $connectionInfo = array("UID"=>"uid", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'UID provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"pwd", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'PWD provided'); + unset($connectionInfo); + + $connectionInfo = array("Authentication"=>"SqlPassword", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'Authentication keyword'); + unset($connectionInfo); +} + +function simpleTest($conn) +{ + // Create table + $tableName = 'Simple'; + $col1 = 'Some simple string value'; + + dropTable($conn, $tableName); + + $query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(25))"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("AzureAD access token test: failed to create a table\n"); + } + + // Insert one row + $query = "INSERT INTO $tableName VALUES ('$col1')"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("AzureAD access token test: failed to insert a row\n"); + } + + // Fetch data + $query = "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("AzureAD access token test: failed to fetch a table\n"); + } + + while (sqlsrv_fetch($stmt)) { + $id = sqlsrv_get_field($stmt, 0); + if ($id != 1) { + fatalError("AzureAD access token test: fetched id $id unexpected\n"); + } + $field = sqlsrv_get_field($stmt, 1); + if ($field !== $col1) { + fatalError("AzureAD access token test: fetched value $field unexpected\n"); + } + } + + dropTable($conn, $tableName); +} + +// First test some error conditions +connectWithInvalidOptions($server); + +// Then, test with an empty access token +connectWithEmptyAccessToken($server); + +// Next, test with a valid access token and perform some simple tasks +require_once('access_token.inc'); +if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { + $connectionInfo = array("Database"=>$adDatabase, "AccessToken"=>$accToken); + + $conn = sqlsrv_connect($adServer, $connectionInfo); + if ($conn === false) { + fatalError("Could not connect with Azure AD AccessToken.\n"); + } else { + simpleTest($conn); + + sqlsrv_close($conn); + } +} + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_errors.phpt b/test/functional/sqlsrv/sqlsrv_errors.phpt index 064357da..fe70816a 100644 --- a/test/functional/sqlsrv/sqlsrv_errors.phpt +++ b/test/functional/sqlsrv/sqlsrv_errors.phpt @@ -119,7 +119,7 @@ sqlsrv_close returns true even if an error happens. echo "Test successfully done.\n"; ?> --EXPECTF-- -Warning: sqlsrv_close() expects parameter 1 to be resource, boolean given in %Ssqlsrv_errors.php on line %x +Warning: sqlsrv_close() expects parameter 1 to be resource, bool%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array @@ -153,7 +153,7 @@ Array ) -Warning: sqlsrv_free_stmt() expects parameter 1 to be resource, integer given in %Ssqlsrv_errors.php on line %x +Warning: sqlsrv_free_stmt() expects parameter 1 to be resource, int%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array @@ -172,7 +172,7 @@ Warning: sqlsrv_close(): supplied resource is not a valid ss_sqlsrv_conn resourc Warning: sqlsrv_close() expects parameter 1 to be resource, null given in %Ssqlsrv_errors.php on line %x -Warning: sqlsrv_close() expects parameter 1 to be resource, integer given in %Ssqlsrv_errors.php on line %x +Warning: sqlsrv_close() expects parameter 1 to be resource, int%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt new file mode 100644 index 00000000..7c8a16ef --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt @@ -0,0 +1,120 @@ +--TEST-- +Test retrieving null datetime values with statement option ReturnDatesAsStrings as true +--DESCRIPTION-- +Test retrieving null datetime values with statement option ReturnDatesAsStrings as true, +which is false by default. Whether retrieved as strings or date time objects should return +NULLs. +--SKIPIF-- + +--FILE-- + 'buffered', 'ReturnDatesAsStrings' => true); + } else { + $options = array('ReturnDatesAsStrings' => true); + } + + $size = count($columns); + $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 string + if (!is_null($field)) { + echo "Expected null for column $columns[$i] but got: "; + var_dump($field); + } + } + + // Fetch row as an object + sqlsrv_execute($stmt); + $object = sqlsrv_fetch_object($stmt); + + $objArray = (array)$object; // turn the object into an associated array + for ($i = 0; $i < $size; $i++) { + $col = $columns[$i]; + $val = $objArray[$col]; + + if (!is_null($val)) { + echo "Expected null for column $columns[$i] but got: "; + var_dump($val); + } + } +} + +function createTestTable($conn, $tableName, $columns) +{ + // Create the test table of date and time columns + $dataTypes = array('date', 'smalldatetime', 'datetime', 'datetime2', 'datetimeoffset', 'time'); + + $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); + + // Insert null values + $inputData = array($colMeta[0]->colName => null, + $colMeta[1]->colName => null, + $colMeta[2]->colName => null, + $colMeta[3]->colName => null, + $colMeta[4]->colName => null, + $colMeta[5]->colName => null); + $stmt = AE\insertRow($conn, $tableName, $inputData); + if (!$stmt) { + fatalError("Failed to insert data.\n"); + } + sqlsrv_free_stmt($stmt); +} + +function runTest($tableName, $columns, $dateAsString) +{ + // Connect + $conn = AE\connect(array('ReturnDatesAsStrings' => $dateAsString)); + if (!$conn) { + fatalError("Could not connect.\n"); + } + + $query = "SELECT * FROM $tableName"; + testFetch($conn, $query, $columns); + testFetch($conn, $query, $columns, true); + + sqlsrv_close($conn); +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$tableName = "TestNullDateTime"; +$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + +// Connect +$conn = AE\connect(); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +createTestTable($conn, $tableName, $columns); + +runTest($tableName, $columns, true); +runTest($tableName, $columns, false); + +dropTable($conn, $tableName); + +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt new file mode 100644 index 00000000..01cc16f2 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt @@ -0,0 +1,202 @@ +--TEST-- +Test retrieving datetime values with statement option ReturnDatesAsStrings set to true +--DESCRIPTION-- +Test retrieving datetime values with statement option ReturnDatesAsStrings set to true, +which is false by default. The statement option should override the corresponding +connection option ReturnDatesAsStrings. +--SKIPIF-- + +--FILE-- +format('Y-m-d H:i:s.u'); + + // actual datetime value from date time object to string + $dtActual = date_format($actualObj, 'Y-m-d H:i:s.u'); + + return ($dtActual === $dtExpected); +} + +function testNoOption($conn, $tableName, $inputs, $exec) +{ + // Without the statement option, should return datetime values as strings + // because the connection option ReturnDatesAsStrings is set to true + $query = "SELECT * FROM $tableName"; + if ($exec) { + $stmt = sqlsrv_query($conn, $query); + } else { + $stmt = sqlsrv_prepare($conn, $query); + sqlsrv_execute($stmt); + } + + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + + // Compare values only + $diffs = array_diff($inputs, $results); + if (!empty($diffs)) { + echo 'The results are different from the input values: '; + print_r($diffs); + } +} + +function testStmtOption($conn, $tableName, $inputs, $stmtDateAsStr) +{ + // The statement option should always override the connection option + $query = "SELECT * FROM $tableName"; + $options = array('ReturnDatesAsStrings' => $stmtDateAsStr); + $stmt = sqlsrv_query($conn, $query, array(), $options); + + if ($stmtDateAsStr) { + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + + // Compare values only + $diffs = array_diff($inputs, $results); + if (!empty($diffs)) { + echo 'The results are different from the input values: '; + print_r($diffs); + } + } else { + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + + // Expect DateTime Objects in $results + for ($i = 0; $i < count($inputs); $i++) { + if (is_object($results[$i])) { + $matched = compareDateTime($inputs[$i], $results[$i]); + if (!$matched) { + echo "Expected a DateTime object of $inputs[$i] but got: \n"; + var_dump($results[$i]); + } + } else { + echo "Expect a DateTime object but got $results[$i]\n"; + } + } + } +} + +function testFetching($conn, $tableName, $inputs, $columns, $withBuffer) +{ + // The statement option ReturnDatesAsStrings set to true + // Test different fetching + $query = "SELECT * FROM $tableName"; + if ($withBuffer){ + $options = array('Scrollable' => 'buffered', 'ReturnDatesAsStrings' => true); + } else { + $options = array('ReturnDatesAsStrings' => true); + } + + $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 string + if ($field != $inputs[$i]) { + echo "Expected $inputs[$i] for column $columns[$i] but got: "; + var_dump($field); + } + } + + // Fetch row as an object + sqlsrv_execute($stmt); + $object = sqlsrv_fetch_object($stmt); + + $objArray = (array)$object; // turn the object into an associated array + for ($i = 0; $i < $size; $i++) { + $col = $columns[$i]; + $val = $objArray[$col]; + + if ($val != $inputs[$i]) { + echo "Expected $inputs[$i] for column $columns[$i] but got: "; + var_dump($val); + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); +date_default_timezone_set('America/Los_Angeles'); + +// Connect with ReturnDatesAsStrings option set to true +$conn = connect(array('ReturnDatesAsStrings' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Generate input values for the test table +$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), + CONVERT(smalldatetime, SYSDATETIME()), + CONVERT(datetime, SYSDATETIME()), + SYSDATETIMEOFFSET(), + CONVERT(time, SYSDATETIME())'; +$stmt = sqlsrv_query($conn, $query); +$values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +// Connect again with ColumnEncryption data +$conn = AE\connect(array('ReturnDatesAsStrings' => true)); + +// Create the test table of date and time columns +$tableName = 'StmtDateAsString'; +$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); +$dataTypes = array('date', 'datetime2', 'smalldatetime', 'datetime', 'datetimeoffset', 'time'); + +$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); + +// Insert data values +$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); + +// Do not set ReturnDatesAsStrings at statement level +testNoOption($conn, $tableName, $values, true); +testNoOption($conn, $tableName, $values, false); + +// Set ReturnDatesAsStrings to false at statement level +testStmtOption($conn, $tableName, $values, false); + +sqlsrv_close($conn); + +// Now connect but with ReturnDatesAsStrings option set to false +$conn = AE\connect(array('ReturnDatesAsStrings' => false)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Set ReturnDatesAsStrings to true at statement level +testStmtOption($conn, $tableName, $values, true); + +// Test fetching by setting ReturnDatesAsStrings to true at statement level +testFetching($conn, $tableName, $values, $columns, true); +testFetching($conn, $tableName, $values, $columns, false); + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt new file mode 100644 index 00000000..4e3add8a --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt @@ -0,0 +1,113 @@ +--TEST-- +Test retrieving datetime values as output params with statement option ReturnDatesAsStrings +--DESCRIPTION-- +Test retrieving datetime values as output params with statement option ReturnDatesAsStrings +with sqlsrv_prepare. When ReturnDatesAsStrings option is false, expect an error to return. +--SKIPIF-- + +--FILE-- + $dateAsString)); + if (!$stmt) { + fatalError("Failed when preparing to call $storedProcName"); + } + $result = sqlsrv_execute($stmt); + if ($dateAsString) { + // Expect to succeed when returning a DateTime value as a string + // The output param value should be the same as the input value + if (!$result) { + fatalError("Failed when invoking $storedProcName"); + } + if ($outDateStr != $inputValue) { + echo "Expected $inputValue but got $outDateStr\n"; + } + } else { + // Expect to fail with an error message because setting a DateTime object as the + // output parameter is not allowed + if ($result) { + fatalError("Returning DateTime as output param is expected to fail!"); + } + // Check if the error message is the expected one + $error = sqlsrv_errors()[0]['message']; + $message = 'An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters'; + if (strpos($error, $message) === false) { + print_r(sqlsrv_errors()); + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); +date_default_timezone_set('America/Los_Angeles'); + +// Connect with ReturnDatesAsStrings option set to true +$conn = connect(array('ReturnDatesAsStrings' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Generate input values for the test table +$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), SYSDATETIMEOFFSET(), CONVERT(time, CURRENT_TIMESTAMP)'; +$stmt = sqlsrv_query($conn, $query); +$values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +// Connect again with ColumnEncryption data +$conn = AE\connect(array('ReturnDatesAsStrings' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Create the test table of date and time columns +$tableName = 'OuputParamDateAsString'; +$columns = array('c1', 'c2', 'c3', 'c4'); +$dataTypes = array('date', 'datetime2', 'datetimeoffset', 'time'); +$sqlTypes = array(SQLSRV_SQLTYPE_DATE, + SQLSRV_SQLTYPE_DATETIME2, + SQLSRV_SQLTYPE_DATETIMEOFFSET, + SQLSRV_SQLTYPE_TIME); +$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])); +AE\createTable($conn, $tableName, $colMeta); + +// Insert data values +$inputData = array($colMeta[0]->colName => $values[0], + $colMeta[1]->colName => $values[1], + $colMeta[2]->colName => $values[2], + $colMeta[3]->colName => $values[3]); +$stmt = AE\insertRow($conn, $tableName, $inputData); +if (!$stmt) { + fatalError("Failed to insert data.\n"); +} +sqlsrv_free_stmt($stmt); + +for ($i = 0; $i < count($columns); $i++) { + // create the stored procedure first + $storedProcName = "spDateTimeOutParam" . $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 + runTest($conn, $storedProcName, $values[$i], $sqlTypes[$i], true); + runTest($conn, $storedProcName, $values[$i], $sqlTypes[$i], false); +} + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/test_conn_execute.phpt b/test/functional/sqlsrv/test_conn_execute.phpt index edd97998..55731ef1 100644 --- a/test/functional/sqlsrv/test_conn_execute.phpt +++ b/test/functional/sqlsrv/test_conn_execute.phpt @@ -1,24 +1,24 @@ ---TEST-- -crash caused by a statement being orphaned when an error occurred during sqlsrv_conn_execute. ---SKIPIF-- - ---FILE-- - ---EXPECTREGEX-- -Warning: sqlsrv_fetch_array\(\) expects parameter 1 to be resource, boolean given in .+(\/|\\)test_conn_execute\.php on line 11 -Test successful +--TEST-- +crash caused by a statement being orphaned when an error occurred during sqlsrv_conn_execute. +--SKIPIF-- + +--FILE-- + +--EXPECTREGEX-- +Warning: sqlsrv_fetch_array\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_conn_execute\.php on line 11 +Test successful diff --git a/test/functional/sqlsrv/test_non_alpha_password.phpt b/test/functional/sqlsrv/test_non_alpha_password.phpt index ffd138c9..a288a1d4 100644 --- a/test/functional/sqlsrv/test_non_alpha_password.phpt +++ b/test/functional/sqlsrv/test_non_alpha_password.phpt @@ -1,61 +1,61 @@ ---TEST-- -password with non alphanumeric characters ---DESCRIPTION-- -The first three cases should have no problem connecting. Only the last case fails because the -right curly brace should be escaped with another right brace. -In Azure for this test to pass do not specify any particular database when connecting ---SKIPIF-- - ---FILE-- - "test_password", "pwd" => "! ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password2", "pwd" => "!}} ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}}" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}" )); -if ($conn) -{ - echo( "Shouldn't have connected" ); -} -$errors = sqlsrv_errors(); -echo $errors[0]["message"]; -sqlsrv_close( $conn ); - -print "Test successful"; -?> ---EXPECTREGEX-- -An unescaped right brace \(}\) was found in either the user name or password. All right braces must be escaped with another right brace \(}}\)\. -Warning: sqlsrv_close\(\) expects parameter 1 to be resource, boolean given in .+(\/|\\)test_non_alpha_password\.php on line 45 -Test successful +--TEST-- +password with non alphanumeric characters +--DESCRIPTION-- +The first three cases should have no problem connecting. Only the last case fails because the +right curly brace should be escaped with another right brace. +In Azure for this test to pass do not specify any particular database when connecting +--SKIPIF-- + +--FILE-- + "test_password", "pwd" => "! ;4triou" )); +if (!$conn) +{ + $errors = sqlsrv_errors(); + echo( $errors[0]["message"]); +} +sqlsrv_close( $conn ); + +$conn = toConnect(array( "UID" => "test_password2", "pwd" => "!}} ;4triou" )); +if (!$conn) +{ + $errors = sqlsrv_errors(); + echo( $errors[0]["message"]); +} +sqlsrv_close( $conn ); + +$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}}" )); +if (!$conn) +{ + $errors = sqlsrv_errors(); + echo( $errors[0]["message"]); +} +sqlsrv_close( $conn ); + +$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}" )); +if ($conn) +{ + echo( "Shouldn't have connected" ); +} +$errors = sqlsrv_errors(); +echo $errors[0]["message"]; +sqlsrv_close( $conn ); + +print "Test successful"; +?> +--EXPECTREGEX-- +An unescaped right brace \(}\) was found in either the user name or password. All right braces must be escaped with another right brace \(}}\)\. +Warning: sqlsrv_close\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_non_alpha_password\.php on line 45 +Test successful